/**
 * @file      uc_wiota_spi_com.c
 * @brief
 * @version   1.0
 * @date      2023.12.14
 *
 * @copyright Copyright (c) 2018 UCchip Technology Co.,Ltd. All rights reserved.
 *
 * Change Logs:
 * Date          Author       Notes
 * 2023.12.14    ucchip       the first version
 *
 */

#include "config_api.h"
#include "drv_gpio.h"
#include "drv_gw_power.h"
#include "manager_crc.h"
#include "manager_queue.h"
#include "slave_uc8x88.h"
#include "watchdog_app.h"
#include "wiota_flashing.h"
#include <rtdevice.h>
#include <rtthread.h>
#include <uc_wiota_api.h>

#define DBG_ENABLE
#define DBG_SECTION_NAME "spi_com"
#include "uc_log.h"

// 开启此宏则会统计上下行发送情况
#define ULDL_STATISTICS
// SPI写校验
#define SPI_WRITE_VERITY

// API耗时LOG开关
// #define COST_TIME_LOG

/* dl_irq: GW PF2 ---------> AP GPIO4 */
/* ul_irq: GW PF1 <--------- AP GPIO5 */
// 用于通知AP，下行数据完成的GPIO中断引脚
#define DL_IRQ_PIN GET_PIN(F, 2)
// 用于接收AP，上行数据写完成的GPIO中断引脚
#define UL_IRQ_PIN GET_PIN(F, 1)

// WIoTA SPI命令响应最大超时时间，单位ms
#define WIOTA_CMD_RESP_TIMEOUT (300)

// 网关启动后与AP握手的最大重试次数，超过最大次数则认为AP上电启动异常
#define HAND_SHAKE_MAX_RETRY_CNT (100)
// SPI命令管理链表长度，用于记录过期命令
#define MAX_CMD_NUM (10)
// 收到上行中断后，通过SPI读取数据错误最大次数，主要为crc校验错误
#define MAX_RETRY_CNT (3)

//{网关和AP采用共享内存通信，网关通过SPI读写共享内存，以下为AP共享内存分区信息及定义，该内存为循环buffer
/*|start_addr 0x00336800                                            total_size 14kb                                          end_addr 0x0033A000|*/
/*|   heart_info  |dl_rb_state_buf|             dl_buf              |dl_buf_state|ul_rb_state_buf|             ul_buf              |ul_buf_state|*/
/*|     8 byte    |     8 byte    |            7152 byte            |   4 byte   |    8 byte     |            7152 byte            |   4 byte   |*/

// AP心跳信息内存大小，八路网关使用，单路网关暂不使用
#define HEART_INFO_SIZE (8)
// 存储读写指针状态的内存大小，单位：字节（下同）
#define RB_STATE_SIZE (4)
// 存储下行数据的内存大小
#define DL_RING_BUFFER_SIZE (7152)
// 存储上行行数据的内存大小
#define UL_RING_BUFFER_SIZE (7152)
// 存储上行或下行握手信息的内存大小
#define SPI_RING_BUFFER_STAT_SIZE (4)
// 共享内存数据四字节对齐
#define RING_BUFFER_ALIGN_SIZE (4)

// 共享内存起始地址
#define SPI_START_ADDRESS (0x00336800)
// 共享内存结束地址
#define SPI_END_ADDRESS (0x0033A000)

// AP心跳信息起始地址，八路网关使用，单路网关暂不使用
#define SPI_HEART_INFO_ADDR SPI_START_ADDRESS

// 下行读指针起始地址
#define SPI_DL_RB_READ_ADDR (SPI_HEART_INFO_ADDR + HEART_INFO_SIZE)
// 下行写指针起始地址
#define SPI_DL_RB_WRITE_ADDR (SPI_DL_RB_READ_ADDR + RB_STATE_SIZE)
// 下行数据起始地址
#define SPI_DL_BUF_ADDR (SPI_DL_RB_WRITE_ADDR + RB_STATE_SIZE)
// 下行握手信息起始地址
#define SPI_DL_BUF_STAT_ADDR (SPI_DL_BUF_ADDR + DL_RING_BUFFER_SIZE)

// 上行读指针起始地址
#define SPI_UL_RB_READ_ADDR (SPI_DL_BUF_STAT_ADDR + SPI_RING_BUFFER_STAT_SIZE)
// 上行写指针起始地址
#define SPI_UL_RB_WRITE_ADDR (SPI_UL_RB_READ_ADDR + RB_STATE_SIZE)
// 上行数据起始地址
#define SPI_UL_BUF_ADDR (SPI_UL_RB_WRITE_ADDR + RB_STATE_SIZE)
// 上行握手信息起始地址
#define SPI_UL_BUF_STAT_ADDR (SPI_END_ADDRESS - SPI_RING_BUFFER_STAT_SIZE)

// AP帧号地址
#define FRAME_NUM_ADDR (SPI_UL_BUF_ADDR + UL_RING_BUFFER_SIZE - 4)

// logical check : complier will notify if any problem
#if (SPI_UL_BUF_STAT_ADDR != SPI_UL_BUF_ADDR + UL_RING_BUFFER_SIZE)
#error SPI addr allocate error
#endif

#if (0 != (RING_BUFFER_ALIGN_SIZE % RT_ALIGN_SIZE))
#error SPI ringbuffer align size not align with RT_ALIGN_SIZE
#endif
//} 共享内存定义结束

//{以下为WIoTa命令枚举
/**
 * @brief WIoTa执行类命令枚举
 *
 */
typedef enum
{
    WIOTA_INIT = 0,             /**< WIoTa初始化 */
    WIOTA_RUN,                  /**< WIoTa运行 */
    WIOTA_EXIT,                 /**< WIoTa退出 */
    WIOTA_REBOOT,               /**< WIoTa重启 */
    WIOTA_DFE_COUNTER_FLAG_SET, /**< 八路网关使用 */
    WIOTA_DFE_COUNTER_SEND = 5, /**< 八路网关使用 */
    WIOTA_PAGING_TX_START,      /**< WIoTa连续信号唤醒开始 */
    WIOTA_MASTER_READY,         /**< 八路网关使用 */
    WIOTA_TS_START,             /**< WIoTa授时开始 */
    WIOTA_TS_STOP,              /**< WIoTa授时停止 */
    WIOTA_REG_FN_REFRESH_CB,    /**< WIoTa注册帧号刷新回调函数 */
} uc_wiota_exec_cmd_e;

/**
 * @brief WIoTa设置类命令枚举
 *
 */
typedef enum
{
    WIOTA_ACT_SET = 0,            /**< WIoTa连接态时间*/
    WIOTA_CONFIG_SET,             /**< WIoTa子网配置 */
    WIOTA_POWER_SET,              /**< WIoTa功率 */
    WIOTA_FREQ_SET,               /**< WIoTa工作频点 */
    WIOTA_HOPPING_FREQ_SET,       /**< WIoTa跳频频点 */
    WIOTA_HOPPING_MODE_SET = 5,   /**< WIoTa跳频模式 */
    WIOTA_MAX_IOTE_NUM_SET,       /**< WIoTa最大连接态终端数量 */
    WIOTA_DATA_RATE_SET,          /**< WIoTa速率模式 */
    WIOTA_BC_MCS_SET,             /**< WIoTa广播速率 */
    WIOTA_CRC_SET,                /**< WIoTa CRC校验 */
    WIOTA_LOG_SET = 10,           /**< WIoTa LOG切和开关 */
    WIOTA_DEFAULT_DFE_SET,        /**< 八路网关使用 */
    WIOTA_FB_ALIGN_SET,           /**< WIoTa开启或关闭帧边界校准功能 */
    WIOTA_PAGING_TX_SET,          /**< WIoTa连续信号唤醒配置 */
    WIOTA_BC_FN_CYCLE_SET,        /**< WIoTa广播帧发送周期设置 */
    WIOTA_BC_SEND_ROUND_SET = 15, /**< WIoTa广播发送轮数，默认3轮 */
    WIOTA_TS_FUNC_SET,            /**< WIoTa授时模式设置 */
    WIOTA_TS_CYCLE_SET,           /**< WIoTa授时周期设置 */
    WIOTA_1588_SYNC_SET,          /**< 同步1588协议授时时间，暂不支持 */
    WIOTA_PPS_SET,                /**< 开启同步助手PPS校验，用于检测同步精度 */
    WIOTA_SINGLE_TONE_SET = 20,   /**< WIoTa单独发送单音信号 */
    WIOTA_SUBF_MODE_SET,          /**< WIoTa设置上行子帧模式，用于上行语音数据接收 */
    WIOTA_SUBF_MODE_CFG_SET,      /**< WIoTa语音编码块大小设置 */
    WIOTA_SM_RESEND_TIMES_SET,    /**< WIoTa单播协议层重发次数，默认为3次 */
    WIOTA_PAGING_RX_SET,          /**< WIoTa连续信号唤醒接收配置 */
    WIOTA_AAGC_IDX_SET = 25,      /**< WIoTa设置aagc */
    WIOTA_BNACK_FUNC_SET,         /**< WIoTa设置BNACK功能 */
} uc_wiota_setup_cmd_e;

/**
 * @brief WIoTa查询类命令枚举
 *
 */
typedef enum
{
    WIOTA_ACT_GET = 0,        /**< WIoTa连接态时间*/
    WIOTA_CONFIG_GET,         /**< WIoTa子网配置 */
    WIOTA_FREQ_GET,           /**< WIoTa工作频点 */
    WIOTA_MAX_IOTE_NUM_GET,   /**< WIoTa最大连接态终端数量 */
    WIOTA_DATA_RATE_GET,      /**< WIoTa速率模式 */
    WIOTA_BC_MCS_GET = 5,     /**< WIoTa广播速率 */
    WIOTA_CRC_GET,            /**< WIoTa CRC校验 */
    WIOTA_FRAME_HEAD_DFE_GET, /**< WIoTa帧头DFE */
    WIOTA_DFE_COUNTER_GET,    /**< AP当前DFE */
    WIOTA_AP_STATE_GET,       /**< WIoTa AP状态 */
    WIOTA_PAGING_TX_GET = 10, /**< WIoTa连续信号唤醒配置 */
    WIOTA_BC_FN_CYCLE_GET,    /**< WIoTa广播帧发送周期设置 */
    WIOTA_BC_SEND_ROUND_GET,  /**< WIoTa广播发送轮数 */
    WIOTA_FRAME_LEN_GET,      /**< WIoTa当前配置帧长 */
    WIOTA_FRAME_NUM_GET,      /**< WIoTa当前帧号 */
    WIOTA_TS_FUNC_GET = 15,   /**< WIoTa授时模式设置 */
    WIOTA_PPS_GET,            /**< 开启同步助手PPS校验 */
} uc_wiota_query_cmd_e;

/**
 * @brief WIoTa可以非阻塞调用的发送类命令枚举
 *
 */
typedef enum
{
    WIOTA_SEND_DATA = 0,   /**< 发送单播 */
    WIOTA_SEND_BC,         /**< 发送广播、OTA */
    WIOTA_SEND_MC,         /**< 发送组播 */
    WIOTA_SCAN_FREQ,       /**< 扫频 */
    WIOTA_TEMP_QUERY,      /**< 基带温度查询 */
    WIOTA_SYNC_PAGING = 5, /**< 发送周期唤醒信号 */
    WIOTA_RS_SM_BY_FN,     /**< WIoTa定帧收发短消息 */
} uc_wiota_send_cmd_e;

/**
 * @brief WIoTa其他参数较多的命令枚举
 *
 */
typedef enum
{
    WIOTA_BL_ADD = 0,    /**< 添加黑名单 */
    WIOTA_BL_REMOVE,     /**< 移除黑名单 */
    WIOTA_BL_GET,        /**< 获取黑名单 */
    WIOTA_MC_ID_ADD,     /**< 添加组播ID */
    WIOTA_MC_ID_DEL,     /**< 删除组播ID */
    WIOTA_VERSION_GET,   /**< WIoTa版本号获取 */
    WIOTA_ADD_SUBF_DATA, /**< WIoTa添加下行子帧数据，语音传输专用，优先级小于OTA广播 */
} uc_wiota_other_cmd_e;

/**
 * @brief WIoTa主动上报类命令枚举
 *
 */
typedef enum
{
    WIOTA_RECV_DATA = 0, /**< WIoTa上行数据 */
    WIOTA_PAGING_CTRL,   /**< 见uc_paging_ctrl_callback */
    WIOTA_DROP,          /**< 终端离开连接态提示 */
    WIOTA_TS_STATE,      /**< 授时状态上报 */
    WIOTA_FN_REFRESH,    /**< 帧号刷新 */
} uc_wiota_recv_cmd_e;

/**
 * @brief WIoTa命令类型
 *
 */
typedef enum
{
    CMD_TYPE_EXEC = 0, /**< uc_wiota_exec_cmd_e  执行类，不带参数，阻塞等待执行结果 */
    CMD_TYPE_SETUP,    /**< uc_wiota_setup_cmd_e 设置类，带参数，阻塞等待执行结果 */
    CMD_TYPE_QUERY,    /**< uc_wiota_query_cmd_e 查询类，带参数也可不带参数，阻塞等待执行结果 */
    CMD_TYPE_OTHER,    /**< uc_wiota_send_cmd_e  发送类，带参数，可阻塞也可非阻塞等待执行结果 */
    CMD_TYPE_SEND,     /**< uc_wiota_other_cmd_e 其他类，带参数，但参数较多，阻塞等待执行结果 */
    CMD_TYPE_RECV = 5, /**< uc_wiota_recv_cmd_e  接收类，AP主动上报，网关被动接收 */
} uc_wiota_cmd_type_e;

/**
 * @brief WIoTa命令执行结果枚举
 *
 */
typedef enum
{
    WIOTA_CMD_EXEC_SUC = 0, /**< 执行成功 */
    WIOTA_CMD_EXEC_FAIL = 1 /**< 执行失败 */
} uc_wiota_cmd_exec_res_e;

//{以下为各命令数据结构定义
// 命令组成：cmd_header + data + CRC32，当无data时，CRC32也不需要，因为头本身就有CRC32
/**
 * @brief 命令头信息结构体
 *
 */
typedef struct
{
    unsigned char cmd_type : 3; /**< 命令类型，uc_wiota_cmd_type_e */
    unsigned char cmd : 5;      /**< 命令，uc_wiota_xxx_cmd_e */
    unsigned char channel : 3;  /**< 通道号，单路网关固定为0，八路网关为0~7 */
    unsigned char result : 1;   /**< 命令执行结果， uc_wiota_cmd_exec_res_e*/
    unsigned char reserved : 4; /**< 对齐预留bit */
    unsigned short data_len;    /**< 头后的有效数据长度 */
    unsigned int header_crc;    /**< 头CRC校验值，只有头校验通过才读取后面的数据 */
} cmd_header_t;

/**
 * @brief 执行类命令结构体，只有头
 *
 */
typedef struct
{
    cmd_header_t cmd_header;
} exec_cmd_info_t;

/**
 * @brief 设置类命令结构体，有头和数据
 *
 */
typedef struct
{
    cmd_header_t cmd_header;
    unsigned int in_value[11];
} setup_cmd_info_t;

/**
 * @brief 查询类命令结构体，有头和数据，数据可缺省
 *
 */
typedef struct
{
    cmd_header_t cmd_header;
    unsigned int in_value[2];
} query_cmd_info_t;

/**
 * @brief 执行类和设置类命令执行结果，只有头
 *
 */
typedef struct
{
    cmd_header_t cmd_header;
} exec_result_info_t;

/**
 * @brief 查询类命令执行结果，有头和数据
 *
 */
typedef struct
{
    cmd_header_t cmd_header;
    unsigned int out_value[11];
} query_result_info_t;

/**
 * @brief WIoTa上行数据结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned int user_id;    /**< 终端的ID */
    unsigned int data_type;  /**< 数据类型 uc_recv_data_type_e */
    unsigned short data_len; /**< 上行数据长度 */
    signed char rssi;        /**< 信号强度 */
    unsigned char delay;     /**< 接入delay*/
    unsigned char fn_cnt;    /**< 语音数据的接收计数， data_type为DATA_TYPE_SUBF_DATA时有效*/
    unsigned char group_idx; /**< 终端在帧结构的位置信息group_idx */
    unsigned char burst_idx; /**< 终端在帧结构的位置信burst_idx */
    unsigned char slot_idx;  /**< 终端在帧结构的位置信息slot_idx */
    unsigned int frame_num;  /**< 收到该上行数据时的帧号 */
    unsigned char data[0];   /**< 上行数据 */
} recv_info_t;

/**
 * @brief WIoTa上行paging ctrl消息结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned int user_id;    /**< 进入sync paging睡眠的终端ID */
    unsigned int burst_idx;  /**< 进入sync paging睡眠的终端的burst_idx */
    unsigned int fn_index;   /**< IoTE第一次进入sync paging睡眠的帧号 */
} paging_ctrl_recv_t;

/**
 * @brief WIoTa下行数据结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header;      /**< 命令头 */
    unsigned int user_id;         /**< 终端的ID */
    signed int timeout;           /**< 发送超时时间 */
    unsigned int data_id;         /**< 数据的地址 */
    unsigned char order_business; /**< 顺序业务标识 */
    unsigned char is_block;       /**< 是否阻塞发送标识 */
    unsigned short data_len;      /**< 下行数据长度 */
    unsigned char data[0];        /**< 下行数据 */
} send_info_t;

/**
 * @brief WIoTa下行数据发送结果结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned int user_id;    /**< 终端的ID */
    unsigned int data_id;    /**< 数据的地址 */
    unsigned int result;     /**< 发送结果，uc_result_e */
} send_res_t;

/**
 * @brief 扫频命令结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned int timeout;    /**< 扫频超时时间 */
    unsigned short is_block; /**< 是否阻塞调用标识 */
    unsigned char freq_num;  /**< 扫频频点个数 */
    unsigned char scan_type; /**< 扫频类型 */
    unsigned char freq[0];   /**< 频点 */
} scan_info_t;

/**
 * @brief 扫频结果结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header;     /**< 命令头 */
    unsigned short result;       /**< 扫频结果，uc_result_e*/
    unsigned short data_len;     /**< 扫频结果信息总长度 */
    uc_scan_freq_t freq_info[0]; /**< 扫频结果信息 */
} scan_res_t;

/**
 * @brief 温度查询命令结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned int timeout;    /**< 查询超时时间 */
    int is_block;            /**< 是否阻塞调用标识 */
} temp_info_t;

/**
 * @brief 温度查询结果结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned short temp;     /**< 温度 */
    unsigned short result;   /**< 查询结果，uc_result_e */
} temp_res_t;

/**
 * @brief 周期信号唤醒结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header;       /**< 命令头 */
    unsigned int user_id;          /**< 执行sync paging的user id */
    unsigned int fn_index;         /**< 通过sync paging ctrl消息接收回调（通过uc_wiota_register_sync_paging_callback注册）
                                        返回的IoTE第一次进入sync paging睡眠的帧号 */
    unsigned int detection_period; /**< IoTE的检测周期，和IoTE配置一致 */
    unsigned short send_round;     /**< 发送sync paging请求的轮数，理论上设置为1即可，如果遇到唤不醒的情况可适当增大 */
    unsigned short continue_fn;    /**< 单轮发送sync paging请求的帧数，理论上设置为1即可，设置为1表示只在周期帧发送，设置为2表示在
                                      周期帧前后各加1帧发送，为3表示前后各加2帧，以此类推，但最大值不能超过检测周期（detection_period）
                                      的一半，如果超过将被设为检测周期的一半 */
    unsigned int is_block;         /**< 是否阻塞调用标识 */
} paging_info_t;

/**
 * @brief 温度查询结果结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned int user_id;    /**< 发送paging info的终端ID */
    unsigned int result;     /**< 周期发送结果，只有成功 */
} paging_res_t;

/**
 * @brief 终端离开连接态提示消息结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned int user_id;    /**< 终端ID */
} drop_info_t;

/**
 * @brief 授时状态结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    uc_ts_info_t ts_info;    /**< 授时信息 */
} ts_info_t;

/**
 * @brief AP帧号刷新结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned int frame_num;  /**< AP当前帧号 */
} fn_refresh_t;

/**
 * @brief WIoTa版本信息结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header;              /**< 命令头 */
    unsigned char wiota_version_8088[15]; /**< WIoTa协议栈版本 */
    unsigned char git_info_8088[36];      /**< WIoTa协议栈GIT信息 */
    unsigned char make_time_8088[36];     /**< WIoTa协议栈版本编译时间 */
    unsigned char wiota_version_8288[15]; /**< WIoTa 8288版本 */
    unsigned char git_info_8288[36];      /**< WIoTa 8288 GIT信息 */
    unsigned char make_time_8288[36];     /**< WIoTa 8288编译时间 */
    unsigned int cce_version;             /**< WIoTa基带版本 */
} version_info_t;

/**
 * @brief WIoTa黑名单及组播ID操作结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned int id_num;     /**< 终端ID或组播ID */
    unsigned int id[0];      /**< ID */
} id_info_t;

/**
 * @brief 下行子帧数据发送结构体，语音传输专用
 *
 */
typedef struct
{
    cmd_header_t cmd_header; /**< 命令头 */
    unsigned char na[2];     /**< 保留 */
    unsigned char data_len;  /**< 下行子帧数据长度 */
    unsigned char fn;        /**< 帧号 */
    unsigned char data[0];   /**< 下行子帧数据 */
} subf_data_ex_t;

/**
 * @brief 指定帧接收短消息结构体
 *
 */
typedef struct
{
    cmd_header_t cmd_header;    /**< 命令头 */
    unsigned int user_id;       /**< 终端ID */
    unsigned int data_id;       /**< 数据地址 */
    unsigned int start_recv_fn; /**< 开始接收的帧号 */
    unsigned char recv_fns;     /**< 接收短消息的帧数 */
    unsigned char send_fns;     /**< 发送短消息的帧数 */
    unsigned short data_len;    /**< 数据长度 */
    unsigned char data[0];      /**< 下行数据 */
} rs_sm_by_fn_ex_t;

/**
 * @brief 下行命令发送参数结构体
 *
 */
struct cmd_send_para
{
    cmd_header_t *cmd_header;   /**< 命令头指针，指向命令的头 */
    int para_num;               /**< 该条命令的数据由几部分组成，目前最2部分 */
    void *para_ptr[2];          /**< 数据指针，指向每一部分数据 */
    unsigned short para_len[2]; /**< 每部分数据的长度 */
};

/**
 * @brief 记录超时命令的结构体
 *
 */
typedef struct
{
    rt_list_t node;         /**< 链表节点 */
    unsigned char cmd_type; /**< 命令类型，uc_wiota_cmd_type_e */
    unsigned char cmd;      /**< 命令，uc_wiota_xxx_cmd_e */
} cmd_list_t;

/**
 * @brief 超时命令管理结构体
 *
 */
typedef struct
{
    cmd_list_t *cmd_resource;  /**< 超时命令节点池，目前最大十个，MAX_CMD_NUM */
    rt_list_t cmd_list_null;   /**< 初始节点链表 */
    rt_list_t cmd_list_expire; /**< 超时过期的命令将从null移到expire */
} cmd_info_t;

/**
 * @brief ringbuffer读指针结构体
 *
 */
typedef struct
{
    unsigned short read_index : 15; /**< 读指针位置 */
    unsigned short read_mirror : 1; /**< ringbuffer是否翻转 */
    unsigned char channel;          /**< 通道号，单路网关固定为0，八路网关为0~7 */
    unsigned char read_crc;         /**< 读指针信息CRC检验 */
} rb_read_state_t;

/**
 * @brief ringbuffer写指针结构体
 *
 */
typedef struct
{
    unsigned short write_index : 15; /**< 写指针位置 */
    unsigned short write_mirror : 1; /**< ringbuffer是否翻转 */
    unsigned char channel;           /**< 通道号，单路网关固定为0，八路网关为0~7 */
    unsigned char write_crc;         /**< 写指针信息CRC检验 */
} rb_write_state_t;

/**
 * @brief 网关和AP握手信息结构体
 *
 */
typedef struct
{
    unsigned short state;   /**< 是否ready的状态 */
    unsigned short channel; /**< 网关分配通道号，单路网关固定为0，八路网关为0~7 */
} hand_shake_t;

/**
 * @brief AP状态信息，网关会定时查询该信息，该信息AP每次cce中断更新一次
 *
 */
typedef struct
{
    unsigned int isr_cnt;            /**< AP中断计数 */
    unsigned short crc_limit : 12;   /**< CRC信息，网关启动后默认为1 */
    unsigned short ap8288_state : 1; /**< AP8288状态，0表示异常，1表示正常 */
    unsigned short channel : 3;      /**< 网关分配通道号，单路网关固定为0，八路网关为0~7  */
    unsigned short heart_crc;        /**< CRC16校验值 */
} heart_info_t;
//}

/**
 * @brief 网关和AP SPI通信总结构体
 *
 */
typedef struct
{
    rt_mutex_t exec_lock;            /**< 命令执行锁，防止多线程操作，保护全局变量 */
    rt_mq_t cmd_queue;               /**< 命令结果接收队列 */
    rt_sem_t ul_irq_sem;             /**< 上行中断通知信号量 */
    struct rt_ringbuffer *dl_rb;     /**< 下行ringbuffer */
    struct rt_ringbuffer *ul_rb;     /**< 上行ringbuffer */
    hand_shake_t dl_buf_state;       /**< 下行ringbuffer握手状态 */
    hand_shake_t ul_buf_state;       /**< 上行ringbuffer握手状态 */
    cmd_info_t cmd_info;             /**< 命令管理信息 */
    unsigned char channel_state;     /**< 通道状态，握手成功后置1 */
    unsigned char ul_irq_task_state; /**< 处理上行中断消息的task状态，正常状态为1，反初始化时置0，退出task */
    unsigned char reserved[2];       /**< 对齐预留位 */
    unsigned int data_id_send_cnt;   /**< 下行数据地址查重计数，发送计数 */
    unsigned int data_id_recv_cnt;   /**< 下行数据地址查重计数，接收次数 */
    unsigned int dl_irq_cnt;         /**< 下行产生中断计数 */
    unsigned int ul_irq_cnt;         /**< 上行接收中断计数 */
#ifdef ULDL_STATISTICS               /**< 上下行发送情况统计 */
    unsigned int ul_recv_cnt;        /**< 上行数据接收次数 */
    unsigned int dl_send_total_cnt;  /**< 下行数据发送总次数 */
    unsigned int dl_send_suc_cnt;    /**< 下行数据发送成功次数 */
    unsigned int dl_send_fail_cnt;   /**< 下行数据发送失败次数 */
#endif
} uc_spi_com_t;

//{WIoTa扰码相关
// 用于userid位置查询所需生成扰码的随机初始值
#define ALGO_MULTIPLIER 0x15a4e35
#define ALGO_INCREMENT 0x1

typedef union
{
    unsigned int data; // WIoTa扰码（跟user_id和子网id相关）
    struct
    {
        unsigned int reserve : 2;
        unsigned int slot_idx : 3;
        unsigned int burst_idx : 3;
        unsigned int group_idx : 3;
    } bit_info; // user_id在帧结构的位置bit信息
} scramble_u;   // WIoTa扰码联合体

static unsigned int g_algo_seed = 100;
//}

// 黑名单链表头
static uc_blacklist_t g_blacklist = {0};

//{ WIoTa回调函数，仅在非阻塞调用时生效
// 接收数据回调
static uc_recv g_recv_cb = RT_NULL;
static uc_recv_detail g_recv_detail_cb = RT_NULL;
// IoTE掉线提示回调
static uc_iote_drop g_drop_cb = RT_NULL;
// 发送数据回调（单播、广播、OTA和组播）
static uc_send_callback g_send_cb[WIOTA_SEND_MC + 1] = {RT_NULL};
// 扫频结果回调
static uc_scan_callback g_scan_cb = RT_NULL;
// sync paging控制消息接收回调
static uc_paging_ctrl_callback g_sync_paging_ctrl_cb = RT_NULL;
// sync paging执行结果回调
static uc_paging_callback g_sync_paging_cb = RT_NULL;
// 授时信息回调
static uc_time_service_callback g_ts_cb = RT_NULL;
static uc_time_service_info_callback g_ts_info_cb = RT_NULL;
// AP帧号刷新回调
static uc_fn_refresh_callback g_fn_refresh_cb = RT_NULL;
//}

// AP低功耗唤醒配置信息（AP可以发送连续唤醒信号唤醒对应配置的IoTE）
// Note：具体低功耗配置请查阅相关文档
static uc_lpm_tx_cfg_t g_lpm_tx_cfg_info = {
    .freq = 160,        // 唤醒频点
    .spectrum_idx = 3,  // 频谱
    .bandwidth = 1,     // 带宽
    .symbol_length = 1, // WIoTa符号长度
    .awaken_id = 30,    // 唤醒ID
    .reserved = 0,      // 对齐预留位
    .send_time = 1000,  // 唤醒信号持续时间
    .mode = 0,          // 唤醒模式, 0表示不开启唤醒ID扩展，1表示开启
};

// spi通信全局变量
static uc_spi_com_t g_spi_com = {0};

static unsigned int g_isr_cnt = 0;

/**
 * @brief 伪随机种子
 *
 * @param seed_set 随机种子
 */
static void uc_algo_srand(unsigned int seed_set)
{
    g_algo_seed = seed_set;
}

/**
 * @brief 生成一个32位的伪随机种子
 *
 * @return unsigned int 32位的伪随机种子
 */
static unsigned int uc_algo_rand(void)
{
    g_algo_seed = g_algo_seed * ALGO_MULTIPLIER + ALGO_INCREMENT;
    return ((g_algo_seed >> 16) & 0x7FFF);
}

/**
 * @brief 生成伪随机一个32位整形数
 *
 * @return unsigned int 伪随机一个32位整形数
 */
static unsigned int uc_algo_rand_u32(void)
{
    return ((uc_algo_rand() & 0xFF) << 24) | ((uc_algo_rand() & 0xFF) << 16) | ((uc_algo_rand() & 0xFF) << 8) | (uc_algo_rand() & 0xFF);
}

/**
 * @brief WIoTa黑名单初始化
 *
 */
static void uc_wiota_blacklist_init()
{
    rt_slist_init(&g_blacklist.node);
}

/**
 * @brief WIoTa黑名单反初始化
 *
 */
void uc_wiota_blacklist_deinit(void)
{
    rt_slist_t *node_next = g_blacklist.node.next;

    while (node_next != RT_NULL)
    {
        uc_blacklist_t *curr_node = rt_slist_entry(node_next, uc_blacklist_t, node);
        rt_free(curr_node);
        curr_node = RT_NULL;
        node_next = node_next->next;
    }

    rt_slist_init(&g_blacklist.node);
}

/**
 * @brief 根据ID查找该节点是否存在
 *
 * @param user_id           黑名单ID
 * @return uc_blacklist_t*  存在返回该节点，不存在返回RT_NULL
 */
static uc_blacklist_t *uc_wiota_blacklist_find_node(unsigned int user_id)
{
    uc_blacklist_t *curr_node;

    rt_slist_for_each_entry(curr_node, &g_blacklist.node, node)
    {
        if (curr_node->user_id == user_id)
        {
            return curr_node;
        }
    }
    return RT_NULL;
}

/**
 * @brief 产生一个下行中断，网关写完数据后调用，通知AP读取数据
 *
 */
static void uc_wiota_enable_dl_irq(void)
{
    g_spi_com.dl_irq_cnt++;
    // TRACE_I("dl_irq cnt %u", g_spi_com.dl_irq_cnt);
    rt_pin_write(DL_IRQ_PIN, PIN_HIGH);
    rt_thread_mdelay(3);
    rt_pin_write(DL_IRQ_PIN, PIN_LOW);
}

/**
 * @brief 接收到上行中断后，释放信号量通知上行中断处理线程，读取共享内存数据
 *
 */
static void uc_wiota_handle_ul_irq(void *para)
{
    if (g_spi_com.ul_irq_sem)
    {
        rt_sem_release(g_spi_com.ul_irq_sem);
    }
}

/**
 * @brief 网关和AP握手操作，网关分配通道号，并告知AP自己已经ready和获取AP是否ready
 *
 */
static void uc_wiota_hand_shake_with_ap(void)
{
    int retry_cnt = 0;

    rt_memset(&g_spi_com.dl_buf_state, 0, sizeof(hand_shake_t));
    rt_memset(&g_spi_com.ul_buf_state, 0, sizeof(hand_shake_t));

    g_spi_com.dl_buf_state.state = 1;
    g_spi_com.dl_buf_state.channel = AP_CHIP_INDEX;
    TRACE_I("hand shake state, dl:%d, ul:%d", g_spi_com.dl_buf_state.state, g_spi_com.ul_buf_state.state);
    while (1)
    {
        slave_uc8x88_mem_dummy_init(AP_CHIP_INDEX);
        slave_uc8x88_mem_read_data(AP_CHIP_INDEX, SPI_UL_BUF_STAT_ADDR, (unsigned char *)&g_spi_com.ul_buf_state, SPI_RING_BUFFER_STAT_SIZE);
        if (g_spi_com.ul_buf_state.state == 1)
        {
            slave_uc8x88_mem_write_data(AP_CHIP_INDEX, SPI_DL_BUF_STAT_ADDR, (unsigned char *)&g_spi_com.dl_buf_state, SPI_RING_BUFFER_STAT_SIZE);
            g_spi_com.channel_state = 1;
            TRACE_I("hand shake with ap suc");
            break;
        }
        else
        {
            retry_cnt++;
            if (retry_cnt >= HAND_SHAKE_MAX_RETRY_CNT)
            {
                TRACE_E("hand shake with ap error, reboot gateway now!");
#if 1
                rt_thread_mdelay(100);
                watchdog_reboot();
#else
                uc_wiota_reboot();
                rt_thread_mdelay(4000);
#endif
            }
            else
            {
                rt_thread_mdelay(1000);
            }
        }
    }
}

/**
 * @brief ringbuffer初始化，网关只记录ringbuffer读写指针状态，不分配实际的内存，实际内存在AP上
 *
 * @param rb_size                ringbuffer大小，上下行各7152字节
 * @param buffer_ptr             ringbuffer地址，网关定义为空
 * @return struct rt_ringbuffer* ringbuffer结构体指针
 */
static struct rt_ringbuffer *uc_wiota_ringbuffer_init(unsigned int rb_size, unsigned char *buffer_ptr)
{
    RT_ASSERT(rb_size > 0);

    struct rt_ringbuffer *new_rb = (struct rt_ringbuffer *)rt_malloc(sizeof(struct rt_ringbuffer));
    RT_ASSERT(new_rb);
    rt_memset(new_rb, 0, sizeof(struct rt_ringbuffer));

    new_rb->read_mirror = new_rb->read_index = 0;
    new_rb->write_mirror = new_rb->write_index = 0;

    new_rb->buffer_ptr = buffer_ptr;
    new_rb->buffer_size = RT_ALIGN_DOWN(rb_size, RING_BUFFER_ALIGN_SIZE);

    return new_rb;
}

/**
 * @brief ringbuffer反初始化
 *
 */
static void uc_wiota_ringbuffer_deinit(void)
{
    if (g_spi_com.dl_rb)
    {
        rt_free(g_spi_com.dl_rb);
    }

    if (g_spi_com.ul_rb)
    {
        rt_free(g_spi_com.ul_rb);
    }
}

/**
 * @brief 获取下行ringbuffer的读指针，写入下行数据之前调用
 *
 * @return int 0表示成功，非0表示失败
 */
static int uc_wiota_ringbuffer_get_dl_read_idx(void)
{
    rb_read_state_t dl_rb_read_state = {0};
    unsigned char read_crc = 0;
    int result = 1;

    for (int retry_cnt = 0; retry_cnt < MAX_RETRY_CNT; retry_cnt++)
    {
        slave_uc8x88_mem_read_data(AP_CHIP_INDEX, SPI_DL_RB_READ_ADDR, (unsigned char *)&dl_rb_read_state, RB_STATE_SIZE);
        read_crc = crc8_calc((unsigned char *)&dl_rb_read_state, sizeof(rb_read_state_t) - CRC8_LEN);
        if (dl_rb_read_state.read_crc == read_crc)
        {
            g_spi_com.dl_rb->read_index = dl_rb_read_state.read_index;
            g_spi_com.dl_rb->read_mirror = dl_rb_read_state.read_mirror;
            result = 0;
            break;
        }
        else
        {
            if (retry_cnt == MAX_RETRY_CNT - 1)
            {
                TRACE_E("dl_rb_read_state crc_check error, retry_cnt %d, cnt %u", retry_cnt, g_spi_com.dl_irq_cnt);
            }
        }
    }

    return result;
}

/**
 * @brief 更新下行ringbuffer写指针，写入下行数据之后调用
 *
 */
static int uc_wiota_ringbuffer_set_dl_write_idx(void)
{
    int result = 0;
    rb_write_state_t dl_rb_write_state = {0};
#ifdef SPI_WRITE_VERITY
    rb_write_state_t read_state = {0};
#endif

    dl_rb_write_state.write_index = g_spi_com.dl_rb->write_index;
    dl_rb_write_state.write_mirror = g_spi_com.dl_rb->write_mirror;
    dl_rb_write_state.write_crc = crc8_calc((unsigned char *)&dl_rb_write_state, sizeof(rb_write_state_t) - CRC8_LEN);
#ifdef SPI_WRITE_VERITY
    for (int retry_cnt = 0; retry_cnt < MAX_RETRY_CNT; retry_cnt++)
    {
        slave_uc8x88_mem_write_data(AP_CHIP_INDEX, SPI_DL_RB_WRITE_ADDR, (unsigned char *)&dl_rb_write_state, RB_STATE_SIZE);
        slave_uc8x88_mem_read_data(AP_CHIP_INDEX, SPI_DL_RB_WRITE_ADDR, (unsigned char *)&read_state, RB_STATE_SIZE);
        if (rt_memcmp(&dl_rb_write_state, &read_state, sizeof(rb_write_state_t)) == 0)
        {
            break;
        }
        else
        {
            if (retry_cnt == MAX_RETRY_CNT - 1)
            {
                TRACE_E("write_idx verify error");
                result = 1;
            }
        }
    }
#else
    slave_uc8x88_mem_write_data(AP_CHIP_INDEX, SPI_DL_RB_WRITE_ADDR, (unsigned char *)&dl_rb_write_state, RB_STATE_SIZE);
#endif

    return result;
}

/**
 * @brief 获取上行ringbuffer写指针，读取上行数据之前调用
 *
 * @return int
 */
static int uc_wiota_ringbuffer_get_ul_write_idx(void)
{
    rb_write_state_t ul_rb_write_state = {0};
    unsigned char write_crc = 0;
    int result = 1;

    for (int retry_cnt = 0; retry_cnt < MAX_RETRY_CNT; retry_cnt++)
    {
        slave_uc8x88_mem_read_data(AP_CHIP_INDEX, SPI_UL_RB_WRITE_ADDR, (unsigned char *)&ul_rb_write_state, RB_STATE_SIZE);
        write_crc = crc8_calc((unsigned char *)&ul_rb_write_state, sizeof(rb_write_state_t) - CRC8_LEN);
        if (ul_rb_write_state.write_crc == write_crc)
        {
            g_spi_com.ul_rb->write_index = ul_rb_write_state.write_index;
            g_spi_com.ul_rb->write_mirror = ul_rb_write_state.write_mirror;
            result = 0;
            break;
        }
        else
        {
            if (retry_cnt == MAX_RETRY_CNT - 1)
            {
                TRACE_E("ul_rb_write_state crc_check error, retry_cnt %d, cnt %u", retry_cnt, g_spi_com.ul_irq_cnt);
            }
        }
    }

    return result;
}

/**
 * @brief 更新上行ringbuffer读指针，读取上行数据之后调用
 *
 */
static void uc_wiota_ringbuffer_set_ul_read_idx(void)
{
    rb_read_state_t ul_rb_read_state = {0};
#ifdef SPI_WRITE_VERITY
    rb_read_state_t read_state = {0};
#endif

    ul_rb_read_state.read_index = g_spi_com.ul_rb->read_index;
    ul_rb_read_state.read_mirror = g_spi_com.ul_rb->read_mirror;
    ul_rb_read_state.read_crc = crc8_calc((unsigned char *)&ul_rb_read_state, sizeof(rb_read_state_t) - CRC8_LEN);
#ifdef SPI_WRITE_VERITY
    for (int retry_cnt = 0; retry_cnt < MAX_RETRY_CNT; retry_cnt++)
    {
        slave_uc8x88_mem_write_data(AP_CHIP_INDEX, SPI_UL_RB_READ_ADDR, (unsigned char *)&ul_rb_read_state, RB_STATE_SIZE);
        slave_uc8x88_mem_read_data(AP_CHIP_INDEX, SPI_UL_RB_READ_ADDR, (unsigned char *)&read_state, RB_STATE_SIZE);
        if (rt_memcmp(&ul_rb_read_state, &read_state, sizeof(rb_write_state_t)) == 0)
        {
            break;
        }
        else
        {
            if (retry_cnt == MAX_RETRY_CNT - 1)
            {
                TRACE_E("read_idx verify error");
            }
        }
    }
#else
    slave_uc8x88_mem_write_data(AP_CHIP_INDEX, SPI_UL_RB_READ_ADDR, (unsigned char *)&ul_rb_read_state, RB_STATE_SIZE);
#endif
}

/**
 * @brief 根据读写指针获取本次中断的数据长度
 *
 * @return unsigned short 数据长度
 */
static unsigned short uc_wiota_ringbuffer_get_ul_data_len(void)
{
    unsigned short ul_data_len = rt_ringbuffer_data_len(g_spi_com.ul_rb);

    if (ul_data_len == 0)
    {
        // TRACE_I("ul_rb:%04d,%d,%04d,%d, no data, cnt %u",
        //            g_spi_com.ul_rb->write_index,
        //            g_spi_com.ul_rb->write_mirror,
        //            g_spi_com.ul_rb->read_index,
        //            g_spi_com.ul_rb->read_mirror,
        //            g_spi_com.ul_irq_cnt);
        return 0;
    }

    if ((ul_data_len < sizeof(cmd_header_t)) || (ul_data_len > g_spi_com.ul_rb->buffer_size))
    {
        TRACE_E("ul_data_len %d invalid", ul_data_len);
        return 0;
    }

    return ul_data_len;
}

/**
 * @brief 将数据写入下行ringbuffer
 *
 * @param rb              下行ringbuffer结构体指针
 * @param write_addr      写入的共享内存地址，SPI_DL_BUF_ADDR
 * @param ptr             下行数据
 * @param length          下行数据长度
 * @return unsigned short 0表示成功，非0表示失败
 */
static unsigned short uc_wiota_ringbuffer_put(struct rt_ringbuffer *rb,
                                              unsigned int write_addr,
                                              unsigned char *ptr,
                                              unsigned short length)
{
    RT_ASSERT(rb != RT_NULL);

    unsigned short write_len = RT_ALIGN(length, RING_BUFFER_ALIGN_SIZE);
    unsigned short space_length = 0;

    space_length = rt_ringbuffer_space_len(rb);

    /* no space */
    if (space_length == 0)
    {
        TRACE_E("no space, dl_rb:%04d,%d,%04d,%d, len %04d, cnt %u",
                rb->write_index, rb->write_mirror, rb->read_index, rb->read_mirror, write_len, g_spi_com.dl_irq_cnt);
        return 0;
    }

    /* space not enough */
    if (space_length < write_len)
    {
        TRACE_E("space not enough, dl_rb:%04d,%d,%04d,%d, len %04d, cnt %u",
                rb->write_index, rb->write_mirror, rb->read_index, rb->read_mirror, write_len, g_spi_com.dl_irq_cnt);
        return 0;
    }

#ifdef SPI_WRITE_VERITY
    unsigned char *read_ptr = (unsigned char *)rt_malloc(write_len);
    RT_ASSERT(read_ptr);
#endif

    if (rb->buffer_size - rb->write_index > write_len)
    {
#ifdef SPI_WRITE_VERITY
        for (int i = 0; i < MAX_RETRY_CNT; i++)
        {
            slave_uc8x88_mem_write_data(AP_CHIP_INDEX, write_addr + rb->write_index, ptr, write_len);
            slave_uc8x88_mem_read_data(AP_CHIP_INDEX, write_addr + rb->write_index, read_ptr, write_len);
            if (rt_memcmp(ptr, read_ptr, write_len) == 0)
            {
                rb->write_index += write_len;
                break;
            }
            else
            {
                if (i == MAX_RETRY_CNT - 1)
                {
                    TRACE_E("write_data verify error, dl_rb:%04d,%d,%04d,%d, len %04d, cnt %u",
                            rb->write_index, rb->write_mirror, rb->read_index, rb->read_mirror, write_len, g_spi_com.dl_irq_cnt);
                    write_len = 0;
                }
            }
        }
#else
        /* read_index - write_index = empty space */
        slave_uc8x88_mem_write_data(AP_CHIP_INDEX, write_addr + rb->write_index, ptr, write_len);
        /* this should not cause overflow because there is enough space for
         * write_len of data in current mirror */
        rb->write_index += write_len;
#endif
    }
    else
    {
#ifdef SPI_WRITE_VERITY
        for (int i = 0; i < MAX_RETRY_CNT; i++)
        {
            slave_uc8x88_mem_write_data(AP_CHIP_INDEX,
                                        write_addr + rb->write_index,
                                        &ptr[0],
                                        rb->buffer_size - rb->write_index);
            slave_uc8x88_mem_write_data(AP_CHIP_INDEX,
                                        write_addr,
                                        &ptr[rb->buffer_size - rb->write_index],
                                        write_len - (rb->buffer_size - rb->write_index));
            slave_uc8x88_mem_read_data(AP_CHIP_INDEX, write_addr + rb->write_index, &read_ptr[0], rb->buffer_size - rb->write_index);
            slave_uc8x88_mem_read_data(AP_CHIP_INDEX, write_addr, &read_ptr[rb->buffer_size - rb->write_index], write_len - (rb->buffer_size - rb->write_index));
            if (rt_memcmp(ptr, read_ptr, write_len) == 0)
            {
                rb->write_mirror = ~rb->write_mirror;
                rb->write_index = write_len - (rb->buffer_size - rb->write_index);
                break;
            }
            else
            {
                if (i == MAX_RETRY_CNT - 1)
                {
                    TRACE_E("write_data verify error, dl_rb:%04d,%d,%04d,%d, len %04d, cnt %u",
                            rb->write_index, rb->write_mirror, rb->read_index, rb->read_mirror, write_len, g_spi_com.dl_irq_cnt);
                    write_len = 0;
                }
            }
        }
#else
        slave_uc8x88_mem_write_data(AP_CHIP_INDEX,
                                    write_addr + rb->write_index,
                                    &ptr[0],
                                    rb->buffer_size - rb->write_index);
        slave_uc8x88_mem_write_data(AP_CHIP_INDEX,
                                    write_addr,
                                    &ptr[rb->buffer_size - rb->write_index],
                                    write_len - (rb->buffer_size - rb->write_index));

        /* we are going into the other side of the mirror */
        rb->write_mirror = ~rb->write_mirror;
        rb->write_index = write_len - (rb->buffer_size - rb->write_index);
#endif
    }

#ifdef SPI_WRITE_VERITY
    rt_free(read_ptr);
#endif

    return write_len;
}

/**
 * @brief 从上行ringbuffer中读取数据
 *
 * @param rb              下行ringbuffer结构体指针
 * @param read_addr       读取的共享内存地址，SPI_UL_BUF_ADDR + offset（有多段数据时有offset）
 * @param ptr             上行数据
 * @param length          上行数据长度
 * @return unsigned short 0表示成功，非0表示失败
 */
static unsigned short uc_wiota_ringbuffer_get_force(struct rt_ringbuffer *rb,
                                                    unsigned int read_addr,
                                                    unsigned char *ptr,
                                                    unsigned short length)
{
    RT_ASSERT(rb != RT_NULL);

    unsigned short read_len = RT_ALIGN(length, RING_BUFFER_ALIGN_SIZE);

    if (rb->buffer_size - rb->read_index > read_len)
    {
        /* copy all of data */
        slave_uc8x88_mem_read_data(AP_CHIP_INDEX, read_addr + rb->read_index, ptr, read_len);
    }
    else
    {
        slave_uc8x88_mem_read_data(AP_CHIP_INDEX,
                                   read_addr + rb->read_index,
                                   &ptr[0],
                                   rb->buffer_size - rb->read_index);
        slave_uc8x88_mem_read_data(AP_CHIP_INDEX,
                                   read_addr,
                                   &ptr[rb->buffer_size - rb->read_index],
                                   read_len - (rb->buffer_size - rb->read_index));
    }

    return read_len;
}

/**
 * @brief 更新本地读指针，上行数据解析完成后调用
 *
 * @param rb     上行ringbuffer结构体指针
 * @param length 单次中断读取的上行数据的总长度
 */
static void uc_wiota_ringbuffer_update_read_idx(struct rt_ringbuffer *rb, unsigned short length)
{
    RT_ASSERT(rb != RT_NULL);

    unsigned short read_len = RT_ALIGN(length, RING_BUFFER_ALIGN_SIZE);

    if (rb->buffer_size - rb->read_index > read_len)
    {
        rb->read_index += read_len;
    }
    else
    {
        rb->read_mirror = ~rb->read_mirror;
        rb->read_index = read_len - (rb->buffer_size - rb->read_index);
    }
}

/**
 * @brief 命令管理资源池和链表初始化
 *
 */
static void uc_wiota_cmd_info_init(void)
{
    int cmd_resource_size = sizeof(cmd_list_t) * MAX_CMD_NUM;

    g_spi_com.cmd_info.cmd_resource = (cmd_list_t *)rt_malloc(cmd_resource_size);
    RT_ASSERT(g_spi_com.cmd_info.cmd_resource);
    rt_memset(g_spi_com.cmd_info.cmd_resource, 0, cmd_resource_size);

    rt_list_init(&g_spi_com.cmd_info.cmd_list_null);
    rt_list_init(&g_spi_com.cmd_info.cmd_list_expire);

    for (int idx = 0; idx < MAX_CMD_NUM; idx++)
    {
        rt_list_insert_after(&g_spi_com.cmd_info.cmd_list_null, &g_spi_com.cmd_info.cmd_resource[idx].node);
    }

    TRACE_D("cmd_info init suc, list_len %d", rt_list_len(&g_spi_com.cmd_info.cmd_list_null));
}

/**
 * @brief 命令管理资源池反初始化
 *
 */
static void uc_wiota_cmd_info_deinit(void)
{
    if (g_spi_com.cmd_info.cmd_resource)
    {
        rt_free(g_spi_com.cmd_info.cmd_resource);
    }
}

/**
 * @brief 命令执行超时，将该命令加入超时过期链表中
 *
 * @param cmd_type 命令类型
 * @param cmd      命令
 */
static void uc_wiota_cmd_add_to_expire_list(unsigned char cmd_type, unsigned char cmd)
{
    rt_list_t *cmd_list_null = &g_spi_com.cmd_info.cmd_list_null;
    rt_list_t *cmd_list_expire = &g_spi_com.cmd_info.cmd_list_expire;
    rt_list_t *next_node = RT_NULL;
    cmd_list_t *cmd_node = RT_NULL;
    cmd_list_t *cmd_node_temp = RT_NULL;

    if (!rt_list_isempty(cmd_list_null))
    {
        next_node = cmd_list_null->next;
        rt_list_remove(next_node);

        cmd_node = rt_list_entry(next_node, cmd_list_t, node);
        cmd_node->cmd_type = cmd_type;
        cmd_node->cmd = cmd;

        rt_list_insert_after(cmd_list_expire, &cmd_node->node);
        TRACE_D("cmd %d %d null to expire %d", cmd_type, cmd, rt_list_len(cmd_list_expire));
    }
    else
    {
        int cnt = 0;
        rt_list_for_each_entry_safe(cmd_node, cmd_node_temp, cmd_list_expire, node)
        {
            rt_list_remove(&cmd_node->node);
            rt_list_insert_after(cmd_list_null, &cmd_node->node);
            cnt++;
            if (cnt == MAX_CMD_NUM / 2)
            {
                break;
            }
        }
        TRACE_D("expire to null %d", rt_list_len(cmd_list_null));
    }

    return;
}

/**
 * @brief 检查解析后的上行命令是否为过期命令，是则需要丢弃
 *
 * @param cmd_type 命令类型
 * @param cmd      命令
 * @return int     0表示为过期命令，非0表示不是过期命令
 */
static int uc_wiota_cmd_check_expire_list(unsigned char cmd_type, unsigned char cmd)
{
    rt_list_t *cmd_list_expire = &g_spi_com.cmd_info.cmd_list_expire;
    rt_list_t *cmd_list_null = &g_spi_com.cmd_info.cmd_list_null;
    cmd_list_t *cmd_node = RT_NULL;
    cmd_list_t *cmd_node_temp = RT_NULL;

    rt_list_for_each_entry_safe(cmd_node, cmd_node_temp, cmd_list_expire, node)
    {
        if (cmd_node->cmd_type == cmd_type && cmd_node->cmd == cmd)
        {
            rt_list_remove(&cmd_node->node);
            rt_list_insert_after(cmd_list_null, &cmd_node->node);
            TRACE_D("cmd %d %d expire to null %d", cmd_type, cmd, rt_list_len(cmd_list_null));
            return 0;
        }
    }

    return -1;
}

/**
 * @brief 释放上行解析子数据，一般情况下，一个中断只有一个子数据，少数情况存在多个子数据
 *
 * @param sub_data
 */
static void uc_wiota_sub_data_free(void *sub_data)
{
    RT_ASSERT(sub_data);

    rt_free(sub_data);
    sub_data = RT_NULL;
}

/**
 * @brief 下行数据封装及发送总接口
 *
 * @param cmd_info
 * @return int
 */
static int uc_wiota_send_data_to_ap(struct cmd_send_para *cmd_info)
{
    unsigned short total_len = 0; // include head + (data + crc32)
    unsigned short data_len = 0;
    unsigned short align_data_len = 0;
    unsigned char *write_buf = RT_NULL;
    unsigned short write_len = 0;
    unsigned short result = 0;
    cmd_header_t cmd_header[2] = {0}; // header + header_backup

    rt_mutex_take(g_spi_com.exec_lock, RT_WAITING_FOREVER);
    if (g_spi_com.channel_state == 0)
    {
        TRACE_W("AP not ready, sending data not allowed");
        rt_mutex_release(g_spi_com.exec_lock);
        return 0;
    }
    // add header len
    total_len += sizeof(cmd_header_t);

    // add data len
    for (int i = 0; i < cmd_info->para_num; i++)
    {
        data_len += cmd_info->para_len[i];
        align_data_len = RT_ALIGN(data_len, RING_BUFFER_ALIGN_SIZE);
    }
    total_len += align_data_len;

    // write cmd header
    cmd_info->cmd_header->data_len = data_len;
    cmd_info->cmd_header->result = WIOTA_CMD_EXEC_SUC;
    cmd_info->cmd_header->header_crc = crc32_calc((unsigned char *)cmd_info->cmd_header, sizeof(cmd_header_t) - CRC32_LEN);

    // 有数据，则需要在添加完数据后再加CRC
    if (align_data_len > 0)
    {
        // add crc32 len
        total_len += CRC32_LEN;
        total_len *= 2; // 原数据 + 原数据的备份数据，如果AP读取原数据校验出错，再读取备份数据
        write_buf = rt_malloc(total_len);
        if (RT_NULL == write_buf)
        {
            TRACE_E("%s line %d rt_malloc fail", __FUNCTION__, __LINE__);
            rt_mutex_release(g_spi_com.exec_lock);
            return 0;
        }
        rt_memset(write_buf, 0, total_len);
        rt_memcpy(write_buf, (unsigned char *)cmd_info->cmd_header, sizeof(cmd_header_t));
        write_len += sizeof(cmd_header_t);

        // copy data
        for (int i = 0; i < cmd_info->para_num; i++)
        {
            rt_memcpy(&write_buf[write_len], cmd_info->para_ptr[i], cmd_info->para_len[i]);
            write_len += cmd_info->para_len[i];
        }
        // data align 4byte
        write_len += (align_data_len - data_len);

        // add crc
        *(unsigned int *)&write_buf[write_len] = crc32_calc(write_buf, write_len);
        write_len += CRC32_LEN;

        // 原数据备份
        rt_memcpy(&write_buf[write_len], write_buf, write_len);
        write_len *= 2;
    }
    else
    {
        // only header，not add crc
        rt_memcpy(&cmd_header[0], cmd_info->cmd_header, sizeof(cmd_header_t));
        rt_memcpy(&cmd_header[1], cmd_info->cmd_header, sizeof(cmd_header_t));
        write_buf = (unsigned char *)cmd_header;
        write_len = sizeof(cmd_header);
    }

    // get dl_rb read_index from ap
    if (0 == uc_wiota_ringbuffer_get_dl_read_idx())
    {
        // put data to dl_rb
        result = uc_wiota_ringbuffer_put(g_spi_com.dl_rb, SPI_DL_BUF_ADDR, write_buf, write_len);
        if (result == write_len)
        {
            // update dl_rb write_index to ap
            if (0 == uc_wiota_ringbuffer_set_dl_write_idx())
            {
                uc_wiota_enable_dl_irq();
            }
            else
            {
                result = 0;
            }
        }

        // TRACE_I("dl_rb:%04d,%d,%04d,%d, len %04d, cnt %u",
        //         g_spi_com.dl_rb->write_index,
        //         g_spi_com.dl_rb->write_mirror,
        //         g_spi_com.dl_rb->read_index,
        //         g_spi_com.dl_rb->read_mirror,
        //         result,
        //         g_spi_com.dl_irq_cnt);
    }

    if (align_data_len > 0)
    {
        rt_free(write_buf);
    }
    rt_mutex_release(g_spi_com.exec_lock);

    return result;
}

/**
 * @brief 等待命令直接结果
 *
 * @param cmd_type 命令类型
 * @param cmd      命令
 * @param timeout  超时时间
 * @return void*   成功返回接收到的结果数据，失败发挥RT_NULL，并将该命令加入过期链表
 */
static void *uc_wiota_recv_response(unsigned char cmd_type, unsigned char cmd, int timeout)
{
    void *cmd_recv = RT_NULL;

    if (RT_EOK != manager_recv_queue(g_spi_com.cmd_queue, (void *)&cmd_recv, timeout))
    {
        uc_wiota_cmd_add_to_expire_list(cmd_type, cmd);
    }

    return cmd_recv;
}
// cmd api over

/**
 * @brief 处理发送类命令结果消息
 *
 * @param cmd            命令
 * @param sub_data       数据
 * @return unsigned char 阻塞调用为1，非阻塞调用为0
 */
static unsigned char uc_wiota_handle_send_cmd_msg(unsigned char cmd, unsigned char *sub_data)
{
    unsigned char if_sync = RT_FALSE;
    send_res_t *send_recv = (send_res_t *)sub_data;
    uc_send_recv_t send_res = {0};

    switch (cmd)
    {
    case WIOTA_SEND_DATA:
    case WIOTA_SEND_BC:
    case WIOTA_SEND_MC:
        send_res.data_id = send_recv->data_id;
        send_res.user_id = send_recv->user_id;
        send_res.result = send_recv->result;
#ifdef ULDL_STATISTICS
        if (send_res.result == UC_OP_SUCC)
        {
            g_spi_com.dl_send_suc_cnt++;
        }
        else
        {
            g_spi_com.dl_send_fail_cnt++;
        }
#endif
        if (send_res.data_id != 0)
        {
            g_spi_com.data_id_recv_cnt++;
            if (g_spi_com.data_id_recv_cnt > g_spi_com.data_id_send_cnt)
            {
                g_spi_com.data_id_recv_cnt = g_spi_com.data_id_send_cnt;
                TRACE_E("maybe ul_data parse error");
                if_sync = RT_TRUE;
                break;
            }
        }

        if (g_send_cb[cmd])
        {
            g_send_cb[cmd](&send_res);
        }
        else
        {
            if_sync = RT_TRUE;
        }
        break;

    case WIOTA_SCAN_FREQ:
        if (g_scan_cb)
        {
            scan_res_t *scan_res = (scan_res_t *)sub_data;
            uc_scan_recv_t scan_recv = {0};
            int freq_info_len = 0;

            scan_recv.freq_num = scan_res->data_len / sizeof(uc_scan_freq_t);
            scan_recv.result = scan_res->result;
            freq_info_len = scan_res->data_len;
            if (scan_recv.result == UC_OP_SUCC)
            {
                uc_scan_freq_t *freq_info = (uc_scan_freq_t *)rt_malloc(freq_info_len);
                RT_ASSERT(freq_info);
                rt_memset(freq_info, 0, freq_info_len);
                rt_memcpy((unsigned char *)freq_info, (unsigned char *)scan_res->freq_info, freq_info_len);

                scan_recv.freq_info = freq_info;
                g_scan_cb(&scan_recv);
            }
        }
        else
        {
            if_sync = RT_TRUE;
        }
        break;

    case WIOTA_TEMP_QUERY:
        if_sync = RT_TRUE;
        break;

    case WIOTA_SYNC_PAGING:
        if (g_sync_paging_cb)
        {
            paging_res_t *paging_res = (paging_res_t *)sub_data;
            uc_paging_recv_t paging_recv = {0};

            paging_recv.user_id = paging_res->user_id;
            paging_recv.result = paging_res->result;

            g_sync_paging_cb(&paging_recv);
        }
        else
        {
            if_sync = RT_TRUE;
        }
        break;

    default:
        TRACE_E("recv error cmd %d", cmd);
        RT_ASSERT(0);
        break;
    }

    return if_sync;
}

/**
 * @brief 处理上行数据接收消息，AP主动上报，网关被动接收的
 *
 * @param cmd      命令
 * @param sub_data 数据
 */
static void uc_wiota_handle_recv_cmd_msg(unsigned char cmd, unsigned char *sub_data)
{
    switch (cmd)
    {
    case WIOTA_RECV_DATA:
        if (g_recv_detail_cb)
        {
            recv_info_t *recv_info = (recv_info_t *)sub_data;
            unsigned char *recv_data = RT_NULL;
            uc_recv_detail_t recv_detail = {0};
#ifdef ULDL_STATISTICS
            g_spi_com.ul_recv_cnt++;
#endif
            recv_data = rt_malloc(recv_info->data_len + 1); // add '\0
            if (recv_data)
            {
                rt_memset(recv_data, 0, recv_info->data_len + 1);
                rt_memcpy(recv_data, recv_info->data, recv_info->data_len);

                recv_detail.user_id = recv_info->user_id;
                recv_detail.data_type = recv_info->data_type;
                recv_detail.data = recv_data;
                recv_detail.data_len = recv_info->data_len;
                recv_detail.rssi = recv_info->rssi;
                recv_detail.delay = recv_info->delay;
                recv_detail.fn_cnt = recv_info->fn_cnt;
                recv_detail.group_idx = recv_info->group_idx;
                recv_detail.burst_idx = recv_info->burst_idx;
                recv_detail.slot_idx = recv_info->slot_idx;
                recv_detail.frame_num = recv_info->frame_num;

                // TRACE_D("user_id 0x%08x, data_type %d, data_len %d, rssi %d, delay %d, fn_cnt %d, group_idx %d, burst_idx %d, slot_idx %d, frame_num %d",
                //         recv_info->user_id, recv_info->data_type, recv_info->data_len, recv_info->rssi, recv_info->delay,
                //         recv_info->fn_cnt, recv_info->group_idx, recv_info->burst_idx, recv_info->slot_idx, recv_info->frame_num);
                g_recv_detail_cb(&recv_detail);
            }
        }
        break;

    case WIOTA_PAGING_CTRL:
        if (g_sync_paging_ctrl_cb)
        {
            paging_ctrl_recv_t *paging_ctrl = (paging_ctrl_recv_t *)sub_data;

            g_sync_paging_ctrl_cb(paging_ctrl->user_id, paging_ctrl->burst_idx, paging_ctrl->fn_index);
        }
        break;

    case WIOTA_DROP:
        if (g_drop_cb)
        {
            drop_info_t *drop_info = (drop_info_t *)sub_data;
            g_drop_cb(drop_info->user_id);
        }
        break;

    case WIOTA_TS_STATE:
        if (g_ts_info_cb)
        {
            ts_info_t *ts_info = (ts_info_t *)sub_data;
            g_ts_info_cb(&ts_info->ts_info);
        }
        break;

    case WIOTA_FN_REFRESH:
        if (g_fn_refresh_cb)
        {
            fn_refresh_t *fn = (fn_refresh_t *)sub_data;
            g_fn_refresh_cb(fn->frame_num);
        }
        break;

    default:
        TRACE_E("recv error cmd %d", cmd);
        RT_ASSERT(0);
        break;
    }
}

/**
 * @brief 上行中断数据解析
 *
 * @return int 0表示成功，非0表示失败
 */
static int uc_wiota_ul_data_parse(void)
{
    cmd_header_t *cmd_header = RT_NULL;
    unsigned short ul_data_len = 0;
    unsigned char *ul_data = RT_NULL;
    unsigned short sub_data_len = 0;
    unsigned char *sub_data = RT_NULL;
    unsigned char *sub_data_bak = RT_NULL;
    unsigned char *sub_data_block = RT_NULL;
    unsigned short align_data_len = 0;
    unsigned int calc_crc = 0;
    unsigned int ori_crc = 0;
    unsigned short sub_data_num = 0;
    unsigned short sub_data_offset = 0;
    unsigned int header_crc = 0;
    unsigned char if_sync = RT_FALSE;
    unsigned char if_free_sub_data = RT_TRUE;
    unsigned char if_get_ul_data = RT_TRUE;
    unsigned char header_error_cnt = 0;
    unsigned char sub_data_error_cnt = 0;

    // get ul read_idx
    if (0 != uc_wiota_ringbuffer_get_ul_write_idx())
    {
        return 0;
    }

    // get ul_data len
    ul_data_len = uc_wiota_ringbuffer_get_ul_data_len();
    if (0 == ul_data_len)
    {
        return 0;
    }

    // malloc heap
    ul_data = rt_malloc(ul_data_len);
    if (RT_NULL == ul_data)
    {
        TRACE_E("%s line %d rt_malloc fail", __FUNCTION__, __LINE__);
        return 0;
    }
    rt_memset(ul_data, 0, ul_data_len);

    // parse ul_data
    while (1)
    {
        // all sub_data parse complete or error_cnt more than MAX_RETRY_CNT, exit loop
        if (sub_data_offset >= ul_data_len ||
            header_error_cnt >= MAX_RETRY_CNT ||
            sub_data_error_cnt >= MAX_RETRY_CNT)
        {
            if (if_free_sub_data)
            {
                uc_wiota_sub_data_free(ul_data);
            }
            // update ul_rb read_index to ap
            uc_wiota_ringbuffer_update_read_idx(g_spi_com.ul_rb, ul_data_len);
            uc_wiota_ringbuffer_set_ul_read_idx();
            break;
        }

        if (if_get_ul_data)
        {
            uc_wiota_ringbuffer_get_force(g_spi_com.ul_rb,
                                          SPI_UL_BUF_ADDR + sub_data_offset,
                                          &ul_data[sub_data_offset],
                                          ul_data_len - sub_data_offset);
        }

        sub_data = &ul_data[sub_data_offset];
        cmd_header = (cmd_header_t *)sub_data;

        // cmd_header crc check
        header_crc = crc32_calc((unsigned char *)cmd_header, sizeof(cmd_header_t) - CRC32_LEN);
        if (cmd_header->header_crc != header_crc)
        {
            header_error_cnt++;
            if_get_ul_data = RT_TRUE;
            if (header_error_cnt == MAX_RETRY_CNT)
            {
                TRACE_E("cmd_header crc_check error, header_error_cnt %d", header_error_cnt);
            }
            continue;
        }

        // reset value after cmd_header check ok
        if_get_ul_data = RT_FALSE;
        if_free_sub_data = RT_TRUE;
        if_sync = RT_FALSE;
        sub_data_len = 0;
        align_data_len = 0;
        header_error_cnt = 0;

        // cmd_header len
        sub_data_len += sizeof(cmd_header_t);

        // sub_data_len and cmd_tail_len
        if (cmd_header->data_len > 0)
        {
            align_data_len = RT_ALIGN(cmd_header->data_len, RING_BUFFER_ALIGN_SIZE);
            sub_data_len += align_data_len;
            // crc32 len
            sub_data_len += CRC32_LEN;

            // sub_data crc check
            calc_crc = crc32_calc(sub_data, sub_data_len - CRC32_LEN);
            ori_crc = *(unsigned int *)&sub_data[sub_data_len - CRC32_LEN];
            if (calc_crc != ori_crc)
            {
                sub_data_error_cnt++;
                if_get_ul_data = RT_TRUE;
                TRACE_E("sub_data crc_check error, error_cnt %d", sub_data_error_cnt);
                continue;
            }
        }

        // ul_rb write_idx read error
        if (sub_data_len > ul_data_len)
        {
            TRACE_E("ul_data_len error");
            continue;
        }

        // next sub_data begin pos
        sub_data_offset += sub_data_len;
        // sub_data_num +1 when sub_data check ok
        sub_data_num++;
        sub_data_error_cnt = 0;

        if (0 == uc_wiota_cmd_check_expire_list(cmd_header->cmd_type, cmd_header->cmd))
        {
            continue;
        }

        // TRACE_I("ul_rb:%04d,%d,%04d,%d, len %03d/%04d/%04d, num %d, cnt %u",
        //         g_spi_com.ul_rb->write_index,
        //         g_spi_com.ul_rb->write_mirror,
        //         g_spi_com.ul_rb->read_index,
        //         g_spi_com.ul_rb->read_mirror,
        //         sub_data_len, sub_data_offset, ul_data_len, sub_data_num, g_spi_com.ul_irq_cnt);

        switch (cmd_header->cmd_type)
        {
        case CMD_TYPE_EXEC:
        case CMD_TYPE_SETUP:
        case CMD_TYPE_QUERY:
        case CMD_TYPE_OTHER:
            if_sync = RT_TRUE;
        case CMD_TYPE_SEND:
            if (cmd_header->cmd_type == CMD_TYPE_SEND)
            {
                if_sync = uc_wiota_handle_send_cmd_msg(cmd_header->cmd, sub_data);
            }
            if (RT_TRUE == if_sync)
            {
                if (sub_data_offset < ul_data_len || sub_data_num > 1)
                {
                    sub_data_bak = rt_malloc(sub_data_len);
                    if (RT_NULL == sub_data_bak)
                    {
                        TRACE_E("%s line %d rt_malloc fail", __FUNCTION__, __LINE__);
                        break;
                    }
                    rt_memset(sub_data_bak, 0, sub_data_len);
                    rt_memcpy(sub_data_bak, sub_data, sub_data_len);
                    sub_data_block = sub_data_bak;
                }
                else
                {
                    sub_data_block = sub_data;
                    if_free_sub_data = RT_FALSE;
                }
                if (RT_EOK != manager_send_queue(g_spi_com.cmd_queue, sub_data_block, WIOTA_CMD_RESP_TIMEOUT))
                {
                    TRACE_E("%s line %d manager_send_queue error", __FUNCTION__, __LINE__);
                    if (if_free_sub_data)
                    {
                        uc_wiota_sub_data_free(sub_data_block);
                    }
                }
            }
            break;

        case CMD_TYPE_RECV:
            uc_wiota_handle_recv_cmd_msg(cmd_header->cmd, sub_data);
            break;

        default:
            TRACE_E("recv error cmd_type %d", cmd_header->cmd_type);
            RT_ASSERT(0);
            break;
        }
    }

    return sub_data_offset;
}

/**
 * @brief 上行中断处理任务
 *
 * @param para 任务创建时传入的参数
 */
static void uc_wiota_handle_ul_irq_msg(void *para)
{
    while (g_spi_com.ul_irq_task_state)
    {
        if (RT_EOK != rt_sem_take(g_spi_com.ul_irq_sem, RT_WAITING_FOREVER))
        {
            continue;
        }
        g_spi_com.ul_irq_cnt++;
        // TRACE_I("ul_irq cnt %d", g_spi_com.ul_irq_cnt);

        if (g_spi_com.channel_state == 0)
        {
            TRACE_W("AP not ready, receving data not allowed");
            continue;
        }

        if (0 == uc_wiota_ul_data_parse())
        {
            continue;
        }
    }
}

/**
 * @brief 网关和AP使用SPI通信反初始化
 *
 */
void uc_wiota_spi_com_deinit(void)
{
    if (g_spi_com.exec_lock)
    {
        rt_mutex_delete(g_spi_com.exec_lock);
    }

    if (g_spi_com.cmd_queue)
    {
        manager_dele_queue(g_spi_com.cmd_queue);
    }

    if (g_spi_com.ul_irq_sem)
    {
        rt_sem_delete(g_spi_com.ul_irq_sem);
    }

    uc_wiota_ringbuffer_deinit();
    uc_wiota_cmd_info_deinit();

    rt_pin_detach_irq(UL_IRQ_PIN);
    rt_pin_irq_enable(UL_IRQ_PIN, PIN_IRQ_DISABLE);

    rt_memset(&g_spi_com, 0, sizeof(uc_spi_com_t));
}

/**
 * @brief 网关和AP使用SPI通信初始化
 *
 */
void uc_wiota_spi_com_init(void)
{
    rt_thread_t ul_irq_handler = RT_NULL;

    // crc初始化，用户数据crc校验计算
    crc8_init();
    crc16_init();
    crc32_init();

    // 创建命令执行互斥锁，保护ringbuffer全局变量
    g_spi_com.exec_lock = rt_mutex_create("cmd_lock", RT_IPC_FLAG_PRIO);
    RT_ASSERT(g_spi_com.exec_lock);

    // 创建命令结果接收队列
    g_spi_com.cmd_queue = (rt_mq_t)manager_create_queue("cmd_mq", 4, 64, RT_IPC_FLAG_PRIO);
    RT_ASSERT(g_spi_com.cmd_queue);

    // 创建上行中断通知信号量
    g_spi_com.ul_irq_sem = rt_sem_create("irq_sem", 0, RT_IPC_FLAG_PRIO);
    RT_ASSERT(g_spi_com.ul_irq_sem);

    // 上下行ringbuffer初始化
    g_spi_com.dl_rb = uc_wiota_ringbuffer_init(DL_RING_BUFFER_SIZE, RT_NULL);
    RT_ASSERT(g_spi_com.dl_rb);
    g_spi_com.ul_rb = uc_wiota_ringbuffer_init(UL_RING_BUFFER_SIZE - 4, RT_NULL); // 上行ringbuffer最后四个字节用于存储帧号
    RT_ASSERT(g_spi_com.ul_rb);

    // 下行中断PIN脚初始化
    rt_pin_mode(DL_IRQ_PIN, PIN_MODE_OUTPUT);
    rt_pin_write(DL_IRQ_PIN, PIN_LOW);

    // 上行中断接收PIN脚初始化，并注册中断回调函数
    rt_pin_mode(UL_IRQ_PIN, PIN_MODE_INPUT);
    rt_pin_attach_irq(UL_IRQ_PIN, PIN_IRQ_MODE_RISING, uc_wiota_handle_ul_irq, RT_NULL);
    rt_pin_irq_enable(UL_IRQ_PIN, PIN_IRQ_ENABLE);

    // 命令管理初始化
    uc_wiota_cmd_info_init();

    // 创建上行中断读取和数据解析任务
    g_spi_com.ul_irq_task_state = 1;
    ul_irq_handler = rt_thread_create("ul_irq",
                                      uc_wiota_handle_ul_irq_msg,
                                      RT_NULL,
                                      2048,
                                      6,
                                      3);
    RT_ASSERT(ul_irq_handler);

    rt_thread_startup(ul_irq_handler);

    rt_thread_mdelay(300);
    // 网关与AP握手，相互确定状态
    uc_wiota_hand_shake_with_ap();
}

/**
 * @brief exec类SPI指令执行接口
 *
 * @param cmd  执行类命令枚举值
 * @return int 0表示成功，非0表示失败
 */
static int uc_wiota_cmd_exec(uc_wiota_exec_cmd_e cmd)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    int result = RT_ERROR;
    exec_cmd_info_t exec_cmd = {0};
    exec_result_info_t *exec_result = RT_NULL;
    struct cmd_send_para cmd_info = {0};

    exec_cmd.cmd_header.cmd_type = CMD_TYPE_EXEC;
    exec_cmd.cmd_header.cmd = cmd;
    exec_cmd.cmd_header.channel = AP_CHIP_INDEX;

    cmd_info.cmd_header = &exec_cmd.cmd_header;
    cmd_info.para_num = 0;

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        exec_result = uc_wiota_recv_response(CMD_TYPE_EXEC, cmd, WIOTA_CMD_RESP_TIMEOUT);
        if (exec_result)
        {
            if (exec_result->cmd_header.result == WIOTA_CMD_EXEC_SUC &&
                exec_result->cmd_header.cmd_type == exec_cmd.cmd_header.cmd_type &&
                exec_result->cmd_header.cmd == exec_cmd.cmd_header.cmd &&
                exec_result->cmd_header.channel == exec_cmd.cmd_header.channel)
            {
                result = RT_EOK;
            }
            uc_wiota_sub_data_free(exec_result);
        }
        else
        {
            result = RT_ETIMEOUT;
        }
    }
#ifdef COST_TIME_LOG
    TRACE_D("exec_cmd %d result %d cost_time %d", cmd, result, rt_tick_get() - tick);
#endif

    return result;
}

/**
 * @brief setup类SPI指令执行接口
 *
 * @param cmd          设置类命令枚举值
 * @param in_value     设置参数
 * @param in_value_num 设置参数个数
 * @return int         0表示成功，非0表示失败
 */
static int uc_wiota_cmd_setup(unsigned char cmd, unsigned int *in_value, unsigned int in_value_num)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    int result = RT_ERROR;
    setup_cmd_info_t setup_cmd = {0};
    exec_result_info_t *exec_result = RT_NULL;
    struct cmd_send_para cmd_info = {0};

    if (RT_NULL == in_value || 0 == in_value_num || in_value_num > 11)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return -1;
    }

    setup_cmd.cmd_header.cmd_type = CMD_TYPE_SETUP;
    setup_cmd.cmd_header.cmd = cmd;
    setup_cmd.cmd_header.channel = AP_CHIP_INDEX;

    cmd_info.cmd_header = &setup_cmd.cmd_header;
    cmd_info.para_num = 1;
    cmd_info.para_ptr[0] = in_value;
    cmd_info.para_len[0] = in_value_num * sizeof(int);

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        exec_result = uc_wiota_recv_response(CMD_TYPE_SETUP, cmd, WIOTA_CMD_RESP_TIMEOUT);
        if (exec_result)
        {
            if (exec_result->cmd_header.result == WIOTA_CMD_EXEC_SUC &&
                exec_result->cmd_header.cmd_type == setup_cmd.cmd_header.cmd_type &&
                exec_result->cmd_header.cmd == setup_cmd.cmd_header.cmd &&
                exec_result->cmd_header.channel == setup_cmd.cmd_header.channel)
            {
                result = RT_EOK;
            }
            uc_wiota_sub_data_free(exec_result);
        }
        else
        {
            result = RT_ETIMEOUT;
        }
    }
#ifdef COST_TIME_LOG
    TRACE_D("setup_cmd %d result %d cost_time %d", cmd, result, rt_tick_get() - tick);
#endif
    return result;
}

/**
 * @brief query类SPI指令执行接口
 *
 * @param cmd           查询类命令枚举值
 * @param in_value      查询结果返回关键字
 * @param in_value_num  查询结果表达式
 * @param out_value     期望查询结果接收buf
 * @param out_value_num 期望查询结果个数
 * @return int          0表示成功，非0表示失败
 */
static int uc_wiota_cmd_query(unsigned char cmd, unsigned int *in_value, unsigned int in_value_num, unsigned int *out_value, unsigned int out_value_num)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    int result = RT_ERROR;
    query_cmd_info_t query_cmd = {0};
    query_result_info_t *query_result = RT_NULL;
    struct cmd_send_para cmd_info = {0};

    query_cmd.cmd_header.cmd_type = CMD_TYPE_QUERY;
    query_cmd.cmd_header.cmd = cmd;
    query_cmd.cmd_header.channel = AP_CHIP_INDEX;

    cmd_info.cmd_header = &query_cmd.cmd_header;
    if (in_value && in_value_num > 0 && in_value_num <= 2)
    {
        cmd_info.para_num = 1;
        cmd_info.para_ptr[0] = in_value;
        cmd_info.para_len[0] = in_value_num * sizeof(int);
    }
    else
    {
        cmd_info.para_num = 0;
    }

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        query_result = uc_wiota_recv_response(CMD_TYPE_QUERY, cmd, WIOTA_CMD_RESP_TIMEOUT);
        if (query_result)
        {
            if (query_result->cmd_header.result == WIOTA_CMD_EXEC_SUC &&
                query_result->cmd_header.cmd_type == query_cmd.cmd_header.cmd_type &&
                query_result->cmd_header.cmd == query_cmd.cmd_header.cmd &&
                query_result->cmd_header.channel == query_cmd.cmd_header.channel)
            {
                if (out_value)
                {
                    rt_memcpy(out_value, query_result->out_value, out_value_num * sizeof(int));
                }
                result = RT_EOK;
            }
            uc_wiota_sub_data_free(query_result);
        }
        else
        {
            result = RT_ETIMEOUT;
        }
    }
#ifdef COST_TIME_LOG
    TRACE_D("query_cmd %d result %d cost_time %d", cmd, result, rt_tick_get() - tick);
#endif
    return result;
}

/**
 * @brief 获取WIoTa的版本号，构建时间等信息
 *
 * @param wiota_version_8088 AP8088的协议栈版本
 * @param git_info_8088      AP8088的git最新一条的信息
 * @param make_time_8088     AP8088的协议栈版本构建时间
 * @param wiota_version_8288 AP8288的协议栈版本
 * @param git_info_8288      AP8288的git最新一条的信息
 * @param make_time_8288     AP8288的协议栈版本构建时间
 * @param cce_version        AP CCE版本
 * @return int               0表示成功，非0表示失败
 */
int uc_wiota_get_version(char *wiota_version_8088, char *git_info_8088, char *make_time_8088,
                         char *wiota_version_8288, char *git_info_8288, char *make_time_8288,
                         int *cce_version)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    int result = RT_ERROR;
    exec_cmd_info_t exec_cmd = {0};
    version_info_t *version_info = RT_NULL;
    struct cmd_send_para cmd_info = {0};

    exec_cmd.cmd_header.cmd_type = CMD_TYPE_OTHER;
    exec_cmd.cmd_header.cmd = WIOTA_VERSION_GET;
    exec_cmd.cmd_header.channel = AP_CHIP_INDEX;

    cmd_info.cmd_header = &exec_cmd.cmd_header;
    cmd_info.para_num = 0;

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        version_info = uc_wiota_recv_response(CMD_TYPE_OTHER, WIOTA_VERSION_GET, WIOTA_CMD_RESP_TIMEOUT);
        if (version_info)
        {
            if (version_info->cmd_header.result == WIOTA_CMD_EXEC_SUC &&
                version_info->cmd_header.cmd_type == exec_cmd.cmd_header.cmd_type &&
                version_info->cmd_header.cmd == exec_cmd.cmd_header.cmd &&
                version_info->cmd_header.channel == exec_cmd.cmd_header.channel)
            {
                if (wiota_version_8088)
                {
                    rt_memcpy(wiota_version_8088, version_info->wiota_version_8088, sizeof(version_info->wiota_version_8088));
                }

                if (git_info_8088)
                {
                    rt_memcpy(git_info_8088, version_info->git_info_8088, sizeof(version_info->git_info_8088));
                }

                if (make_time_8088)
                {
                    rt_memcpy(make_time_8088, version_info->make_time_8088, sizeof(version_info->make_time_8088));
                }

                if (wiota_version_8288)
                {
                    rt_memcpy(wiota_version_8288, version_info->wiota_version_8288, sizeof(version_info->wiota_version_8288));
                }

                if (git_info_8288)
                {
                    rt_memcpy(git_info_8288, version_info->git_info_8288, sizeof(version_info->git_info_8288));
                }

                if (make_time_8288)
                {
                    rt_memcpy(make_time_8288, version_info->make_time_8288, sizeof(version_info->make_time_8288));
                }

                if (cce_version)
                {
                    *cce_version = version_info->cce_version;
                }

                result = RT_EOK;
            }
            uc_wiota_sub_data_free(version_info);
        }
        else
        {
            result = RT_ETIMEOUT;
        }
    }
#ifdef COST_TIME_LOG
    TRACE_D("get_version result %d cost_time %d", result, rt_tick_get() - tick);
#endif
    return result;
}

/**
 * @brief WIoTa协议栈初始化接口
 *
 * @return int 0表示成功，非0表示失败
 */
int uc_wiota_init(void)
{
    int result = RT_EOK;

    result = uc_wiota_cmd_exec(WIOTA_INIT);

    if (result == RT_EOK)
    {
        uc_wiota_blacklist_init();
    }
    return result;
}

/**
 * @brief WIoTa协议栈运行接口
 *
 * @return int 0表示成功，非0表示失败
 */
int uc_wiota_run(void)
{
    return uc_wiota_cmd_exec(WIOTA_RUN);
}

/**
 * @brief WIoTa协议栈退出接口
 *
 * @return int 0表示成功，非0表示失败
 */
int uc_wiota_exit(void)
{
    return uc_wiota_cmd_exec(WIOTA_EXIT);
}

/**
 * @brief 使能AP电源
 *
 */
void uc_wiota_power_on(void)
{
    ap_power_init();
    TRACE_I("ap power on");
}

/**
 * @brief 关闭AP电源
 *
 */
void uc_wiota_power_off(void)
{
    ap_power_deinit();
    TRACE_I("ap power off");
}

/**
 * @brief AP模组重启
 *
 */
void uc_wiota_reboot(void)
{
    ap_power_reset();
    TRACE_I("ap reboot");
}

/**
 * @brief 使能同步助手电源
 *
 */
void uc_wiota_sync_assistant_power_on(void)
{
    sync_assistant_power_init();
    TRACE_I("sync assistant power on");
}

/**
 * @brief 关闭同步助手电源
 *
 */
void uc_wiota_sync_assistant_power_off(void)
{
    sync_assistant_power_deinit();
    TRACE_I("sync assistant power off");
}

/**
 * @brief 同步助手模组重启
 *
 */
void uc_wiota_sync_assistant_reboot(void)
{
    sync_assistant_power_reset();
    TRACE_I("sync assistant reboot");
}

/**
 * @brief 注册IoTE掉线提示回调函数，当IoTE接入后，在连接态时间内未在和AP交互，AP会将该IoTE踢掉，称为IoTE掉线
 *
 * @param callback 掉线提示回调函数指针
 */
void uc_wiota_register_iote_dropped_callback(uc_iote_drop callback)
{
    g_drop_cb = callback;
}

/**
 * @brief 注册IoTE上行数据接收回调函数，当AP收到IoTE上行数据并收完后，触发回调，将接收到的数据传给应用层
 *
 * @param callback 上行数据接收回调函数指针
 */
void uc_wiota_register_recv_data_callback(uc_recv callback)
{
    g_recv_cb = RT_NULL; // SPI通信只支持detail回调
}

/**
 * @brief 注册IoTE上行数据接收回调函数，当AP收到IoTE上行数据并收完后，触发回调，将接收到的数据传给应用层，
 *        相较于uc_wiota_register_recv_data_callback上报信息更加详细，且两个回调只会生效一个，后者生效
 *
 * @param callback 上行数据接收回调函数指针
 */
void uc_wiota_register_recv_data_detail_callback(uc_recv_detail callback)
{
    g_recv_detail_cb = callback;
}

/**
 * @brief 设置WIoTa子网配置
 *
 * @param config WIoTa子网配置结构体指针
 * @return int   0表示成功，非0表示失败
 */
int uc_wiota_set_system_config(sub_system_config_t *config)
{
    if (RT_NULL == config)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    unsigned int in_value[11] = {config->power,
                                 config->id_len,
                                 config->pp,
                                 config->symbol_length,
                                 config->dlul_ratio,
                                 config->bt_value,
                                 config->group_number,
                                 config->spectrum_idx,
                                 config->old_subsys_v,
                                 config->bitscb,
                                 config->subsystem_id};

    return uc_wiota_cmd_setup(WIOTA_CONFIG_SET, in_value, 11);
}

/**
 * @brief 获取WIoTa子网配置
 *
 * @param config WIoTa子网配置结构体指针
 * @return int   0表示成功，非0表示失败
 */
int uc_wiota_get_system_config(sub_system_config_t *config)
{
    int result = RT_EOK;
    unsigned int out_value[11] = {0};

    if (RT_NULL == config)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    result = uc_wiota_cmd_query(WIOTA_CONFIG_GET, RT_NULL, 0, out_value, 11);
    if (result == RT_EOK)
    {
        config->power = out_value[0];
        config->id_len = out_value[1];
        config->pp = out_value[2];
        config->symbol_length = out_value[3];
        config->dlul_ratio = out_value[4];
        config->bt_value = out_value[5];
        config->group_number = out_value[6];
        config->spectrum_idx = out_value[7];
        config->old_subsys_v = out_value[8];
        config->bitscb = out_value[9];
        config->subsystem_id = out_value[10];
    }

    return result;
}

/**
 * @brief 设置WIoTa连接态超时时间，IoTE接入到超时时间结束，称为连接态，在连接态期间收到的消息称为连接态消息，只要IoTE和AP有交互就会重置连接态时间，
 *        超过连接态时间未交互，IoTE掉线
 *
 * @param active_s 连接态超时时间，单位：秒
 * @return int     0表示成功，非0表示失败
 */
int uc_wiota_set_active_time(unsigned int active_s)
{
    return uc_wiota_cmd_setup(WIOTA_ACT_SET, &active_s, 1);
}

/**
 * @brief 获取WIoTa连接态超时时间
 *
 * @return int 连接态超时时间
 */
unsigned int uc_wiota_get_active_time(void)
{
    unsigned int active_time = 0;

    uc_wiota_cmd_query(WIOTA_ACT_GET, RT_NULL, 0, &active_time, 1);

    return active_time;
}

/**
 * @brief 单独设置AP发送功率接口，功率支持热设置（AP运行中也可设置）
 *
 * @param power    AP发射功率，范围0~29dbm
 * @return int     0表示成功，非0表示失败
 */
int uc_wiota_set_ap_tx_power(signed char power)
{
    unsigned int in_value = power;

    if (0 > power || 29 < power)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    TRACE_D("set_tx_power %d", power);

    return uc_wiota_cmd_setup(WIOTA_POWER_SET, &in_value, 1);
}

/**
 * @brief 设置WIoTa工作频点，不支持热配置
 *
 * @param freq_idx 频点，范围：0~200
 * @return int     0表示成功，非0表示失败
 */
int uc_wiota_set_freq_info(unsigned char freq_idx)
{
    unsigned int in_value = freq_idx;

    if (200 < freq_idx)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    TRACE_D("set_freq_idx %d", freq_idx);

    return uc_wiota_cmd_setup(WIOTA_FREQ_SET, &in_value, 1);
}

/**
 * @brief 获取WIoTa工作频点
 *
 * @return unsigned char 0~200表示成功，0xFF表示失败
 */
unsigned char uc_wiota_get_freq_info(void)
{
    unsigned int freq_idx = 0xFF;

    uc_wiota_cmd_query(WIOTA_FREQ_GET, RT_NULL, 0, &freq_idx, 1);

    return freq_idx;
}

/**
 * @brief 设置WIoTa跳频频点，WIoTa支持简单的跳频，即在两个频点上工作
 *
 * @param hopping_freq 跳频频点，不要和工作频点一样，范围：0~200
 * @return int         0表示成功，非0表示失败
 */
int uc_wiota_set_hopping_freq(unsigned char hopping_freq)
{
    unsigned int in_value = hopping_freq;

    if (200 < hopping_freq)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    TRACE_D("set_hopping_freq_idx %d", hopping_freq);

    return uc_wiota_cmd_setup(WIOTA_HOPPING_FREQ_SET, &in_value, 1);
}

/**
 * @brief 设置跳频模式接口，设置了跳频频点还需设置跳频模式，以指定在工作频点和跳频频点工作的帧数
 *
 * @param ori_freq_frame     在原频点（即工作频点）工作的帧数
 * @param hopping_freq_frame 在跳频频点工作的帧数
 * @return int               0表示成功，非0表示失败
 */
int uc_wiota_set_hopping_mode(unsigned char ori_freq_frame, unsigned char hopping_freq_frame)
{
    unsigned int in_value[2] = {ori_freq_frame, hopping_freq_frame};

    TRACE_D("set_hopping_freq_mode %d, %d", ori_freq_frame, hopping_freq_frame);

    return uc_wiota_cmd_setup(WIOTA_HOPPING_MODE_SET, in_value, 2);
}

/**
 * @brief 设置WIoTa最大的连接态终端数量，dlul_ratio为0时默认为72个，dlul_ratio为1时默认为144个，如果有超过该值的需求，在wiota_run之前调用设置
 *
 * @param max_iote_num 最大的连接态终端数量，最好为8的帧数倍，范围1~800
 * @return int         0表示成功，非0表示失败
 */
int uc_wiota_set_max_num_of_active_iote(unsigned short max_iote_num)
{
    unsigned int in_value = max_iote_num;

    if (0 == max_iote_num || 800 < max_iote_num)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    TRACE_D("set_max_iote_num %d", max_iote_num);

    return uc_wiota_cmd_setup(WIOTA_MAX_IOTE_NUM_SET, &in_value, 1);
}

/**
 * @brief 获取WIoTa最大的连接态终端数量
 *
 * @return unsigned short 最大的连接态终端数量，0表示查询失败
 */
unsigned short uc_wiota_get_max_num_of_active_iote(void)
{
    unsigned int max_iote_num = 0;

    uc_wiota_cmd_query(WIOTA_MAX_IOTE_NUM_GET, RT_NULL, 0, &max_iote_num, 1);

    return max_iote_num;
}

/**
 * @brief 设置WIoTa速率模式和对应模式的值
 *        第一种模式，是设置下行单播速率，设置值为uc_mcs_level_e
 *        在第一种模式的基础上，在系统配置中dlul_ratio为1:2时，才能打开第二种模式，打开该模式能够提高该帧结构情况下两倍速率，默认第二种模式关闭
 *        在第一种模式的基础上，打开第三种模式，能够提升8*(1 << group_number)倍单终端的速率，但是会影响网络中其他终端的上行，建议在大数据量快速传输需求时使用
 *
 * @param rate_mode  WIoTa速率模式，对应枚举uc_data_rate_mode_e
 * @param rate_value normal模式取值为uc_mcs_level_e，且在不同symbol_length下支持的速率最大值亦有限制，symbol_length为0，1，2，3时对应的最大速率分别为
 *                   MCS4，MCS6，MCS7和MCS7
 *                   mid模式取值为0或1，表示开启或关闭中速模式，且只能在dlul_ratio为1:2时开启，1:1不支持
 *                   high模式取值为0或1~UC_DL_MAX_LEN，表示关闭或发送长度大于设定值时开启，且只支持dlul_ratio为1:1的配置，1:2不支持
 *                   crc_type表示基带crc的长度，默认为4字节，当crc_type为1时，表示1字节的crc，此种情况下单帧数据量会多三个字节，语音专用其他模式不用开启
 * @return int       0表示成功，非0表示失败
 */
int uc_wiota_set_data_rate(uc_data_rate_mode_e rate_mode, unsigned int rate_value)
{
    unsigned int in_value[2] = {rate_mode, rate_value};
    int result = RT_EOK;

    if (UC_RATE_NORMAL == rate_mode)
    {
        if (UC_MCS_LEVEL_AUTO < rate_value)
        {
            result = -RT_ERROR;
        }
    }
    else if (UC_RATE_MID == rate_mode || UC_RATE_CRC_TYPE == rate_mode)
    {
        if (0 != rate_value && 1 != rate_value)
        {
            result = -RT_ERROR;
        }
    }
    else if (UC_RATE_HIGH == rate_mode)
    {
        if (UC_DL_MAX_LEN < rate_value)
        {
            result = -RT_ERROR;
        }
    }
    else
    {
        result = -RT_ERROR;
    }

    if (result != RT_EOK)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    TRACE_D("set_data_rate mode %d, value %d", rate_mode, rate_value);

    return uc_wiota_cmd_setup(WIOTA_DATA_RATE_SET, in_value, 2);
}

/**
 * @brief 获取WIoTa速率模式的值
 *
 * @param rate_mode     WIoTa速率模式
 * @return unsigned int 参考设置接口的rate_value
 */
unsigned int uc_wiota_get_data_rate_value(uc_data_rate_mode_e rate_mode)
{
    unsigned int in_value = rate_mode;
    unsigned int rate_value = 0;

    if (UC_RATE_HIGH < rate_mode)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    uc_wiota_cmd_query(WIOTA_DATA_RATE_GET, &in_value, 1, &rate_value, 1);

    return rate_value;
}

/**
 * @brief 设置WIoTa广播发送速率
 *
 * @param bc_mcs 广播发送速率，只能为固定MCS，不能为自动MCS，范围：MCS0~MCS6
 *               在不同symbol_length下支持的速率最大值亦有限制，symbol_length为0，1，2，3时对应的最大速率分别为MCS4，MCS6，MCS6和MCS5
 * @return int   0表示成功，非0表示失败
 */
int uc_wiota_set_broadcast_mcs(uc_mcs_level_e bc_mcs)
{
    unsigned int in_value = bc_mcs;

    if (UC_MCS_LEVEL_6 < bc_mcs)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    TRACE_D("set_bc_mcs %d", bc_mcs);

    return uc_wiota_cmd_setup(WIOTA_BC_MCS_SET, &in_value, 1);
}

/**
 * @brief 获取WIoTa广播发送速率
 *
 * @return uc_mcs_level_e 广播发送速率
 */
uc_mcs_level_e uc_wiota_get_broadcast_mcs(void)
{
    unsigned int bc_mcs = UC_MCS_LEVEL_INVALID;

    uc_wiota_cmd_query(WIOTA_BC_MCS_GET, RT_NULL, 0, &bc_mcs, 1);

    return bc_mcs;
}

/**
 * @brief 设置WIoTa广播帧发送周期，默认11帧发送一次，广播帧用于发送一些控制信息和同步帧号，非特殊用途（如配合IoTE睡眠起来后发送数据）不建议修改
 *
 * @param bc_fn_cycle 广播帧发送周期，范围1~11，注意设置为1时，相当于每帧都发广播帧，会导致子帧6无法发送其他下行短消息
 * @return int        0表示成功，非0表示失败
 */
int uc_wiota_set_broadcast_fn_cycle(unsigned char bc_fn_cycle)
{
    unsigned int in_value = bc_fn_cycle;

    if (11 < bc_fn_cycle)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    TRACE_D("set_fn_cycle %d", bc_fn_cycle);

    return uc_wiota_cmd_setup(WIOTA_BC_FN_CYCLE_SET, &in_value, 1);
}

/**
 * @brief 获取WIoTa广播帧发送周期
 *
 * @return unsigned char 广播帧发送周期
 */
unsigned char uc_wiota_get_broadcast_fn_cycle(void)
{
    unsigned int bc_fn_cycle = 0;

    uc_wiota_cmd_query(WIOTA_BC_FN_CYCLE_GET, RT_NULL, 0, &bc_fn_cycle, 1);

    return bc_fn_cycle;
}

/**
 * @brief 设置广播发轮数，默认三轮，非特殊需求不建议更改
 *
 * @param round 发送轮数
 * @return int 0表示成功，非0表示失败
 */
int uc_wiota_set_broadcast_send_round(unsigned char round)
{
    unsigned int in_value = round;

    if (0 == round)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    TRACE_D("send_bc_send_round %d", round);

    return uc_wiota_cmd_setup(WIOTA_BC_SEND_ROUND_SET, &in_value, 1);
}

/**
 * @brief 获取广播发轮数
 *
 * @return unsigned char 发送轮数
 */
unsigned char uc_wiota_get_broadcast_send_round(void)
{
    unsigned int round = 0;

    uc_wiota_cmd_query(WIOTA_BC_SEND_ROUND_GET, RT_NULL, 0, &round, 1);

    return round;
}

/**
 * @brief 获取当前配置的WIoTa帧结构的单帧帧长
 *
 * @return unsigned int 帧长，单位：微妙
 */
unsigned int uc_wiota_get_frame_len(void)
{
    unsigned int frame_len = 0;

    uc_wiota_cmd_query(WIOTA_FRAME_LEN_GET, RT_NULL, 0, &frame_len, 1);

    return frame_len;
}

/**
 * @brief 获取AP当前帧号，帧号存储在上行ringbuffer的最后四个字节，直接读取
 *
 * @return unsigned int 帧号
 */
unsigned int uc_wiota_get_frame_num(void)
{
    unsigned int frame_num = 0;

    slave_uc8x88_mem_read_data(AP_CHIP_INDEX, FRAME_NUM_ADDR, (unsigned char *)&frame_num, 4);

    return frame_num;
}

/**
 * @brief 获取AP8288运行状态
 *
 * @return int 0表示异常，1表示正常，其他表示获取失败
 */
int uc_wiota_get_ap8288_state(void)
{
    unsigned int state = 0;

    uc_wiota_cmd_query(WIOTA_AP_STATE_GET, RT_NULL, 0, &state, 1);

    return state;
}

/**
 * @brief AP状态检查，将网关定时检测crc和ap8288状态合并了，且用单独的共享内存存储该信息
 *
 * @return int 0表示异常，1表示正常
 */
int uc_wiota_check_state(void)
{
    heart_info_t heart_info = {0};
    unsigned short calc_crc16 = 0;
    int state = 0;

    for (int i = 0; i < MAX_RETRY_CNT; i++)
    {
        slave_uc8x88_mem_read_data(AP_CHIP_INDEX, SPI_HEART_INFO_ADDR, (unsigned char *)&heart_info, sizeof(heart_info_t));
        calc_crc16 = crc16_calc((unsigned char *)&heart_info, sizeof(heart_info_t) - CRC16_LEN);
        if (heart_info.heart_crc == calc_crc16 && g_isr_cnt != heart_info.isr_cnt)
        {
            if (heart_info.ap8288_state == 1 && heart_info.crc_limit == 1)
            {
                state = 1;
                g_isr_cnt = heart_info.isr_cnt;
                TRACE_I("wiota_check_state ok, isr_cnt %u", g_isr_cnt);
                break;
            }
            else
            {
                rt_thread_mdelay(10);
            }
        }
        else
        {
            TRACE_E("wiota_check_state error, crc 0x%x 0x%x, cnt %u %u",
                    heart_info.heart_crc, calc_crc16, g_isr_cnt, heart_info.isr_cnt);
            rt_thread_mdelay(10);
        }
    }

    return state;
}

/**
 * @brief 清0AP中断计数
 *
 */
void uc_wiota_isr_cnt_clear(void)
{
    g_isr_cnt = 0;
}

/**
 * @brief 设置WIota发送时携带CRC校验，该接口设置的状态应该和IoTE同步，否则会造成数据接收错误，默认为发送长度大于100字节是添加CRC，该接口限制对所有下行
 *        生效（广播、OTA、组播和单播）
 *
 * @param crc_limit 为0表示关闭CRC，不管数据多长都不添加CRC；大于等于1时，表示发送长度大于设定值时添加CRC，如果要求所有长度数据都添加CRC，设置为1即可
 * @return int      0表示成功，非0表示失败
 */
int uc_wiota_set_crc(unsigned short crc_limit)
{
    unsigned int in_value = crc_limit;

    if (UC_BC_MAX_LEN < crc_limit)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    TRACE_D("set_crc_limit %d", crc_limit);

    return uc_wiota_cmd_setup(WIOTA_CRC_SET, &in_value, 1);
}

/**
 * @brief 获取当前的CRC限制值
 *
 * @return unsigned short 0~UC_BC_MAX_LEN表示成功，0xFFFF表示失败
 */
unsigned short uc_wiota_get_crc(void)
{
    unsigned int crc_limit = 0;

    uc_wiota_cmd_query(WIOTA_CRC_GET, RT_NULL, 0, &crc_limit, 1);

    return crc_limit;
}

/**
 * @brief 获取黑名单并返回黑名单个数
 *
 * @param blacklist_num   黑名单个数
 * @return uc_blacklist_t 黑名单链表头，RT_NULL表示获取失败
 */
uc_blacklist_t *uc_wiota_get_blacklist(unsigned short *blacklist_num)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    exec_cmd_info_t exec_cmd = {0};
    id_info_t *bl_info = RT_NULL;
    struct cmd_send_para cmd_info = {0};

    exec_cmd.cmd_header.cmd_type = CMD_TYPE_OTHER;
    exec_cmd.cmd_header.cmd = WIOTA_BL_GET;
    exec_cmd.cmd_header.channel = AP_CHIP_INDEX;

    cmd_info.cmd_header = &exec_cmd.cmd_header;
    cmd_info.para_num = 0;

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        bl_info = uc_wiota_recv_response(CMD_TYPE_OTHER, WIOTA_BL_GET, WIOTA_CMD_RESP_TIMEOUT * 100);
        if (bl_info)
        {
            if (bl_info->cmd_header.result == WIOTA_CMD_EXEC_SUC &&
                bl_info->cmd_header.cmd_type == exec_cmd.cmd_header.cmd_type &&
                bl_info->cmd_header.cmd == exec_cmd.cmd_header.cmd &&
                bl_info->cmd_header.channel == exec_cmd.cmd_header.channel)
            {
                for (int i = 0; i < bl_info->id_num; i++)
                {
                    uc_blacklist_t *curr_node = uc_wiota_blacklist_find_node(bl_info->id[i]);

                    if (curr_node == RT_NULL)
                    {
                        uc_blacklist_t *new_node = (uc_blacklist_t *)rt_malloc(sizeof(uc_blacklist_t));
                        RT_ASSERT(new_node);
                        rt_memset(new_node, 0, sizeof(uc_blacklist_t));

                        new_node->user_id = bl_info->id[i];

                        rt_slist_append(&g_blacklist.node, &new_node->node);
                    }
                }
                *blacklist_num = bl_info->id_num;
            }
            uc_wiota_sub_data_free(bl_info);
        }
    }
#ifdef COST_TIME_LOG
    TRACE_D("get_bl cost_time %d", rt_tick_get() - tick);
#endif
    return &g_blacklist;
}

static int uc_wiota_handle_idinfo_msg(unsigned int *id, unsigned short id_num, unsigned char cmd)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    int result = RT_ERROR;
    id_info_t bl_info = {0};
    exec_result_info_t *exec_result = RT_NULL;
    struct cmd_send_para cmd_info = {0};

    if (RT_NULL == id || 0 == id_num || (id_num > 255))
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return RT_ERROR;
    }

    bl_info.cmd_header.cmd_type = CMD_TYPE_OTHER;
    bl_info.cmd_header.cmd = cmd;
    bl_info.cmd_header.channel = AP_CHIP_INDEX;

    cmd_info.cmd_header = &bl_info.cmd_header;
    cmd_info.para_num = 2;
    // user_id_num
    cmd_info.para_ptr[0] = &id_num;
    cmd_info.para_len[0] = sizeof(unsigned short);

    // user id
    cmd_info.para_ptr[1] = id;
    cmd_info.para_len[1] = id_num * sizeof(int);

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        exec_result = uc_wiota_recv_response(CMD_TYPE_OTHER, cmd, WIOTA_CMD_RESP_TIMEOUT * id_num);
        if (exec_result)
        {
            if (exec_result->cmd_header.result == WIOTA_CMD_EXEC_SUC &&
                exec_result->cmd_header.cmd_type == bl_info.cmd_header.cmd_type &&
                exec_result->cmd_header.cmd == bl_info.cmd_header.cmd &&
                exec_result->cmd_header.channel == bl_info.cmd_header.channel)
            {
                result = RT_EOK;
            }
            uc_wiota_sub_data_free(exec_result);
        }
        else
        {
            result = RT_ETIMEOUT;
        }
    }
#ifdef COST_TIME_LOG
    TRACE_D("bl_cmd %d result %d cost_time %d", cmd, result, rt_tick_get() - tick);
#endif
    return result;
}

/**
 * @brief 添加一组user id到黑名单
 *
 * @param user_id     要添加的黑名单id数组首地址
 * @param user_id_num 要添加的黑名单id个数
 * @return int        0表示成功，非0表示失败
 */
int uc_wiota_add_iote_to_blacklist(unsigned int *user_id, unsigned short user_id_num)
{
    return uc_wiota_handle_idinfo_msg(user_id, user_id_num, WIOTA_BL_ADD);
}

/**
 * @brief 将一组user id从黑名单中移除
 *
 * @param user_id     要移除的黑名单id数组首地址
 * @param user_id_num 要移除的黑名单id个数
 * @return int        0表示成功，非0表示失败
 */
int uc_wiota_remove_iote_from_blacklist(unsigned int *user_id, unsigned short user_id_num)
{
    return uc_wiota_handle_idinfo_msg(user_id, user_id_num, WIOTA_BL_REMOVE);
}

/**
 * @brief WIoTa协议栈log开关，默认开启串口log，SPI log需要Trace log工具配合才能抓取
 *
 * @param log_type WIoTa协议栈log类型，串口log或SPI log
 * @param is_open  1表示开启log，0表示关闭log
 * @return int     0表示成功，非0表示失败
 */
int uc_wiota_log_switch(uc_log_type_e log_type, unsigned char is_open)
{
    unsigned int in_value[2] = {log_type, is_open};

    if (log_type != UC_LOG_UART && log_type != UC_LOG_SPI)
    {
        TRACE_E("%s line %d input para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    return uc_wiota_cmd_setup(WIOTA_LOG_SET, in_value, 2);
}

/**
 * @brief 发送失败处理函数
 *
 * @param user_id  单播时为IoTE的user id，广播时或OTA则为uc_bc_mode_e，组播时则为组播id
 * @param data_id  用户传入的para
 * @param callback 发送结果回调函数指针
 */
static void uc_wiota_report_send_fail_by_cb(unsigned int user_id, unsigned int data_id, uc_send_callback callback)
{
    if (callback != RT_NULL)
    {
        uc_send_recv_t result = {0};

        result.user_id = user_id;
        result.data_id = data_id;
        result.result = UC_OP_FAIL;
        callback(&result);
    }
}

/**
 * @brief WIoTa发送下行消息处理函数
 *
 * @param cmd            发送AT指令
 * @param send_data      发送数据指针
 * @param send_data_len  发送数据长度
 * @param timeout        发送超时时间
 * @param order_business 顺序业务标识
 * @param callback       发送结果回调函数指针
 * @param send_type      发送类型，广播、组播或单播
 * @return uc_result_e   发送结果，成功、超时或失败
 */
static uc_result_e uc_wiota_handle_send_msg(unsigned char *send_data,
                                            unsigned short send_data_len,
                                            unsigned int id,
                                            signed int timeout,
                                            unsigned int order_business,
                                            uc_send_callback callback,
                                            unsigned int data_id,
                                            unsigned char cmd)
{
    uc_result_e result = UC_OP_FAIL;
    send_res_t *send_recv = RT_NULL;
    send_info_t send_info = {0};
    struct cmd_send_para cmd_info = {0};

    send_info.cmd_header.cmd_type = CMD_TYPE_SEND;
    send_info.cmd_header.cmd = cmd;
    send_info.cmd_header.channel = AP_CHIP_INDEX;
    send_info.user_id = id;
    send_info.timeout = timeout;
    send_info.order_business = order_business;
    send_info.data_id = data_id;
    send_info.is_block = (RT_NULL == callback ? 1 : 0);
    send_info.data_len = send_data_len;

    cmd_info.cmd_header = &send_info.cmd_header;
    cmd_info.para_num = 2;
    cmd_info.para_ptr[0] = ((unsigned char *)&send_info) + sizeof(cmd_header_t);
    cmd_info.para_len[0] = sizeof(send_info_t) - sizeof(cmd_header_t);
    // data
    cmd_info.para_ptr[1] = send_data;
    cmd_info.para_len[1] = send_data_len;

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        if (callback == RT_NULL)
        {
            send_recv = uc_wiota_recv_response(CMD_TYPE_SEND, cmd, timeout < 0 ? RT_WAITING_FOREVER : timeout);
            if (send_recv)
            {
                if (send_info.cmd_header.result == WIOTA_CMD_EXEC_SUC &&
                    send_info.cmd_header.cmd_type == send_recv->cmd_header.cmd_type &&
                    send_info.cmd_header.cmd == send_recv->cmd_header.cmd &&
                    send_info.cmd_header.channel == send_recv->cmd_header.channel)
                {
                    result = send_recv->result;
                }
                else
                {
                    result = UC_OP_FAIL;
                }
                uc_wiota_sub_data_free(send_recv);
            }
            else
            {
                result = UC_OP_TIMEOUT;
            }
        }
        else
        {
#ifdef ULDL_STATISTICS
            g_spi_com.dl_send_total_cnt++;
#endif
            if (data_id != 0)
            {
                g_spi_com.data_id_send_cnt++;
            }
            g_send_cb[cmd] = callback;
            result = UC_OP_SUCC;
        }
    }
    else
    {
        result = UC_OP_FAIL;
        uc_wiota_report_send_fail_by_cb(id, data_id, callback);
    }

    return result;
}

/**
 * @brief WIoTa广播（普通广播和OTA）发送接口
 *
 * @param send_data     要发送的广播数据指针
 * @param send_data_len 要发送的广播数据长度
 * @param mode          广播模式，普通广播或OTA
 * @param timeout       广播发送超时时间，该时间跟帧结构配置、数据量和广播速率相关，如果不好把控，建议往大了设
 * @param callback      发送结果回调，传入RT_NULL时表示阻塞发送，非RT_NULL时表示非阻塞发送
 * @param para          用户自定义参数，该参数会在发送结果回调时传回来，一般传入发送数据的地址，用于知晓每段数据的发送结果
 * @return uc_result_e  成功或超时，广播没有ACK理论上不会有失败
 */
uc_result_e uc_wiota_send_broadcast_data(unsigned char *send_data,
                                         unsigned short send_data_len,
                                         uc_bc_mode_e mode,
                                         signed int timeout,
                                         uc_send_callback callback,
                                         void *para)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    uc_result_e result = UC_OP_FAIL;
    unsigned int data_id = (unsigned int)para;

    if (send_data == RT_NULL || 0 == send_data_len || UC_BC_MAX_LEN < send_data_len || OTA_BROADCAST < mode)
    {
        TRACE_E("%s line %d input param error", __FUNCTION__, __LINE__);
        uc_wiota_report_send_fail_by_cb(mode, data_id, callback);
        return UC_OP_FAIL;
    }

    result = uc_wiota_handle_send_msg(send_data, send_data_len, mode, timeout, 0, callback, data_id, WIOTA_SEND_BC);

#ifdef COST_TIME_LOG
    TRACE_D("send bc result %d cost_time %d", result, rt_tick_get() - tick);
#endif
    return result;
}

/**
 * @brief 设置WIoTa组播id，最多支持设置8个组播id，该id要和IoTE设置的组播接收id一致
 *
 * @param multicast_id 要设置的一组组播id
 * @param id_num       要设置的组播id个数
 * @return int         0表示成功，非0表示失败
 */
int uc_wiota_set_multicast_id(unsigned int *multicast_id, unsigned int id_num)
{
    if (multicast_id == RT_NULL || id_num > 8)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    return uc_wiota_handle_idinfo_msg(multicast_id, id_num, WIOTA_MC_ID_ADD);
}

/**
 * @brief 删除一组WIoTa组播id
 *
 * @param multicast_id 要删除的一组组播id首地址
 * @param id_num       要删除的组播id个数
 * @return int         0表示成功，非0表示失败
 */
int uc_wiota_del_multicast_id(unsigned int *multicast_id, unsigned int id_num)
{
    if (multicast_id == RT_NULL || id_num > 8)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    return uc_wiota_handle_idinfo_msg(multicast_id, id_num, WIOTA_MC_ID_DEL);
}

/**
 * @brief WIoTa组播发送接口
 *
 * @param send_data     要发送的组播数据指针
 * @param send_data_len 要发送的组播数据长度
 * @param multicast_id  组播id，该id必须先通过设置组播id接口设置，否则会返回失败
 * @param timeout       组播发送超时时间，该时间跟帧结构配置、业务类型、数据量和广播速率相关，如果不好把控，建议往大了设
 * @param callback      发送结果回调，传入RT_NULL时表示阻塞发送，非RT_NULL时表示非阻塞发送
 * @param para          用户自定义参数，该参数会在发送结果回调时传回来，一般传入发送数据的地址，用于知晓每段数据的发送结果
 * @return uc_result_e  成功或超时，组播没有ACK理论上不会有失败，除非组播id未先设置
 */
uc_result_e uc_wiota_send_multicast_data(unsigned char *send_data,
                                         unsigned short send_data_len,
                                         unsigned int multicast_id,
                                         signed int timeout,
                                         uc_send_callback callback,
                                         void *para)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    uc_result_e result = UC_OP_FAIL;
    unsigned int data_id = (unsigned int)para;

    if (RT_NULL == send_data || 0 == send_data_len || UC_BC_MAX_LEN < send_data_len || 0 == multicast_id)
    {
        TRACE_E("%s line %d input param error", __FUNCTION__, __LINE__);
        uc_wiota_report_send_fail_by_cb(multicast_id, data_id, callback);
        return UC_OP_FAIL;
    }

    result = uc_wiota_handle_send_msg(send_data, send_data_len, multicast_id, timeout, 0, callback, data_id, WIOTA_SEND_MC);

#ifdef COST_TIME_LOG
    TRACE_D("send mc result %d cost_time %d", result, rt_tick_get() - tick);
#endif
    return result;
}

/**
 * @brief WIoTa单播发送接口
 *
 * @param send_data     要发送的单播数据指针
 * @param send_data_len 要发送的但播数据长度
 * @param user_id       要发送给某个IoTE的user id，该IoTE必须先和AP保持同步状态，否则会发送失败
 * @param timeout       单播发送超时时间，该时间跟帧结构配置、业务类型、数据量和广播速率相关，如果不好把控，建议往大了设
 * @param callback      发送结果回调，传入RT_NULL时表示阻塞发送，非RT_NULL时表示非阻塞发送
 * @param para          用户自定义参数，该参数会在发送结果回调时传回来，一般传入发送数据的地址，用于知晓每段数据的发送结果
 * @return uc_result_e  成功、超时或失败
 */
uc_result_e uc_wiota_send_data(unsigned char *send_data,
                               unsigned short send_data_len,
                               unsigned int user_id,
                               signed int timeout,
                               uc_send_callback callback,
                               void *para)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    uc_result_e result = UC_OP_FAIL;
    unsigned int data_id = (unsigned int)para;

    if (RT_NULL == send_data || 0 == send_data_len || UC_DL_MAX_LEN < send_data_len || 0 == user_id)
    {
        TRACE_E("%s line %d input param error", __FUNCTION__, __LINE__);
        uc_wiota_report_send_fail_by_cb(user_id, data_id, callback);
        return UC_OP_FAIL;
    }

    result = uc_wiota_handle_send_msg(send_data, send_data_len, user_id, timeout, 0, callback, data_id, WIOTA_SEND_DATA);

#ifdef COST_TIME_LOG
    TRACE_D("send data result %d cost_time %d", result, rt_tick_get() - tick);
#endif
    return result;
}

/**
 * @brief WIoTa顺序单播业务发送接口
 *
 * @param send_data      要发送的单播数据指针
 * @param send_data_len  要发送的但播数据长度
 * @param user_id        要发送给某个IoTE的user id，该IoTE必须先和AP保持同步状态，否则会发送失败
 * @param timeout        单播发送超时时间，该时间跟帧结构配置、业务类型、数据量和广播速率相关，如果不好把控，建议往大了设
 * @param order_business 顺序业务标识，为0时和普通单播业务相同，为1时表示该业务为顺序业务，会等待之前的业务终端掉线后再发送
 * @param callback       发送结果回调，传入RT_NULL时表示阻塞发送，非RT_NULL时表示非阻塞发送
 * @param para           用户自定义参数，该参数会在发送结果回调时传回来，一般传入发送数据的地址，用于知晓每段数据的发送结果
 * @return uc_result_e   成功、超时或失败
 */
uc_result_e uc_wiota_send_data_order(unsigned char *send_data,
                                     unsigned short send_data_len,
                                     unsigned int user_id,
                                     signed int timeout,
                                     unsigned int order_business,
                                     uc_send_callback callback,
                                     void *para)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    uc_result_e result = UC_OP_FAIL;
    unsigned int data_id = (unsigned int)para;

    if (RT_NULL == send_data || 0 == send_data_len || UC_DL_MAX_LEN < send_data_len || 0 == user_id)
    {
        TRACE_E("%s line %d input param error", __FUNCTION__, __LINE__);
        uc_wiota_report_send_fail_by_cb(user_id, data_id, callback);
        return UC_OP_FAIL;
    }

    result = uc_wiota_handle_send_msg(send_data, send_data_len, user_id, timeout, order_business, callback, data_id, WIOTA_SEND_DATA);

#ifdef COST_TIME_LOG
    TRACE_D("send data result %d cost_time %d", result, rt_tick_get() - tick);
#endif
    return result;
}

/**
 * @brief 非阻塞调用失败后返回结果
 *
 * @param callback 扫频回调
 */
static void uc_wiota_report_scan_fail_by_cb(uc_scan_callback callback)
{
    if (callback != NULL)
    {
        uc_scan_recv_t result = {0};

        result.result = UC_OP_FAIL;
        callback(&result);
    }
}

/**
 * @brief WIoTa频点扫描接口，用于扫描一组频点，返回每个频点的频点信息
 *
 * @param freq           要扫描的一组频点数组的首地址
 * @param freq_nu        要扫描的一组频点个数
 * @param scan_type      扫频类型，0为普通扫描，执行慢，但结果更准确；1为快速扫描，执行快，但结果相对较差
 * @param timeout        扫频超时，建议1min以上
 * @param callback       扫频结果回调，传入RT_NULL时表示阻塞调用，非RT_NULL时表示非阻塞调用，一般都用阻塞方式
 * @param scan_result    扫频结果，包含了每个频点的信息
 * @return uc_result_e   成功、超时或失败
 */
uc_result_e uc_wiota_scan_freq(unsigned char *freq,
                               unsigned char freq_num,
                               unsigned char scan_type,
                               signed int timeout,
                               uc_scan_callback callback,
                               uc_scan_recv_t *scan_result)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    uc_result_e result = UC_OP_FAIL;
    scan_info_t scan_info = {0};
    scan_res_t *scan_res = RT_NULL;
    uc_scan_freq_t *freq_info = RT_NULL;
    struct cmd_send_para cmd_info = {0};

    scan_info.cmd_header.cmd_type = CMD_TYPE_SEND;
    scan_info.cmd_header.cmd = WIOTA_SCAN_FREQ;
    scan_info.cmd_header.channel = AP_CHIP_INDEX;
    scan_info.timeout = timeout;
    scan_info.is_block = 1;
    scan_info.freq_num = freq_num;
    scan_info.scan_type = scan_type;

    cmd_info.cmd_header = &scan_info.cmd_header;
    cmd_info.para_num = 1;
    cmd_info.para_ptr[0] = ((unsigned char *)&scan_info) + sizeof(cmd_header_t);
    cmd_info.para_len[0] = sizeof(scan_info_t) - sizeof(cmd_header_t);

    // data
    if (freq_num != 0 && freq != RT_NULL)
    {
        cmd_info.para_num++;
        cmd_info.para_ptr[1] = freq;
        cmd_info.para_len[1] = freq_num;
    }

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        if (callback == RT_NULL)
        {
            TRACE_I("waiting for scan freq result...");
            scan_res = uc_wiota_recv_response(CMD_TYPE_SEND, WIOTA_SCAN_FREQ, timeout < 0 ? RT_WAITING_FOREVER : timeout);
            if (scan_res)
            {
                if (scan_info.cmd_header.result == WIOTA_CMD_EXEC_SUC &&
                    scan_info.cmd_header.cmd_type == scan_res->cmd_header.cmd_type &&
                    scan_info.cmd_header.cmd == scan_res->cmd_header.cmd &&
                    scan_info.cmd_header.channel == scan_res->cmd_header.channel)
                {
                    if (scan_res->result == UC_OP_SUCC)
                    {
                        freq_info = (uc_scan_freq_t *)rt_malloc(scan_res->data_len);
                        RT_ASSERT(freq_info);
                        rt_memset(freq_info, 0, scan_res->data_len);
                        rt_memcpy(freq_info, scan_res->freq_info, scan_res->data_len);
                        scan_result->freq_info = freq_info;
                    }
                    scan_result->freq_num = scan_res->data_len / sizeof(uc_scan_freq_t);

                    // set result
                    scan_result->result = scan_res->result;
                    result = scan_res->result;
                }
                else
                {
                    result = UC_OP_FAIL;
                }
                uc_wiota_sub_data_free(scan_res);
            }
            else
            {
                result = UC_OP_TIMEOUT;
            }
        }
        else
        {
            g_scan_cb = callback;
            result = UC_OP_SUCC;
        }
    }
    else
    {
        result = UC_OP_FAIL;
        uc_wiota_report_scan_fail_by_cb(callback);
    }
    scan_result->result = result;

#ifdef COST_TIME_LOG
    TRACE_D("scan freq result %d cost_time %d", result, rt_tick_get() - tick);
#endif
    return result;
}

/**
 * @brief 读取AP模组上AP8288的芯片温度（即基带温度），只有在没有上下行任务时才能读取，否则会直接返回失败
 *
 * @param callback  温度查询回调函数，传入RT_NULL时表示阻塞调用，非RT_NULL时表示非阻塞调用
 * @param read_temp 读取到的温度结果
 * @param timeout   读取超时时间
 * @return int      0表示成功，非0表示失败
 */
int uc_wiota_read_temperature(uc_temp_callback callback, uc_temp_recv_t *read_temp, signed int timeout)
{
#ifdef COST_TIME_LOG
    unsigned int tick = rt_tick_get();
#endif
    uc_result_e result = UC_OP_FAIL;
    temp_info_t temp_info = {0};
    temp_res_t *temp_res = RT_NULL;
    struct cmd_send_para cmd_info = {0};

    if (read_temp == RT_NULL)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return UC_OP_FAIL;
    }

    temp_info.cmd_header.cmd_type = CMD_TYPE_SEND;
    temp_info.cmd_header.cmd = WIOTA_TEMP_QUERY;
    temp_info.cmd_header.channel = AP_CHIP_INDEX;
    temp_info.timeout = timeout;
    temp_info.is_block = 1;

    cmd_info.cmd_header = &temp_info.cmd_header;
    cmd_info.para_num = 1;
    cmd_info.para_ptr[0] = ((unsigned char *)&temp_info) + sizeof(cmd_header_t);
    cmd_info.para_len[0] = sizeof(temp_info_t) - sizeof(cmd_header_t);

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        TRACE_I("waiting for read temp result...");
        temp_res = uc_wiota_recv_response(CMD_TYPE_SEND, WIOTA_TEMP_QUERY, timeout < 0 ? RT_WAITING_FOREVER : timeout);
        if (temp_res)
        {
            if (temp_info.cmd_header.result == WIOTA_CMD_EXEC_SUC &&
                temp_info.cmd_header.cmd_type == temp_res->cmd_header.cmd_type &&
                temp_info.cmd_header.cmd == temp_res->cmd_header.cmd &&
                temp_info.cmd_header.channel == temp_res->cmd_header.channel)
            {
                read_temp->temp = temp_res->temp;
                read_temp->result = temp_res->result;
                result = temp_res->result;
            }
            else
            {
                result = UC_OP_FAIL;
            }
            uc_wiota_sub_data_free(temp_res);
        }
        else
        {
            result = UC_OP_TIMEOUT;
        }
    }
    else
    {
        result = UC_OP_FAIL;
    }
#ifdef COST_TIME_LOG
    TRACE_D("query temp result %d cost_time %d", result, rt_tick_get() - tick);
#endif
    return result;
}

/**
 * @brief 通过user id生成扰码的算法
 *
 * @param id            user id
 * @param scb0          扰码0
 * @param scb1          扰码1
 * @return unsigned int user id对应的扰码
 */
static unsigned int uc_query_scrambleid_by_userid(unsigned int id, unsigned int scb0, unsigned int scb1)
{
    unsigned int scramble_id = 0;
    unsigned int hi, lo, b0, b1;

    /* x0 = ((id[31] shr 1) xor r1)[31:0] */
    unsigned int x0 = (((id & 0x80000000) >> 1) ^ scb1) & 0x7FFFFFFF;
    /* x1 = (id xor r0)[31:0] */
    unsigned int x1 = (id ^ scb0) & 0x7FFFFFFF;
    /* length = (id[7:0] + (id[23:16] shr 16))[7:0] + 20 */
    int length = (((id & 0x7F) + ((id & 0x7F0000) >> 16)) & 0x7F) + 20;

    for (int i = 0; i < length; ++i)
    {
        /* loop xor on x0 */
        /* ------------------------------------------- */
        /*   30  29  28  27  ...  03 | 02  01  00  31' */
        /* ^ 27  26  25  24  ...  00 | 31' 30' 29' 28' */
        /* ------------------------------------------- */
        /*   31' 30' 29' 28' ...  04'| 03' 02' 01' 00' */
        /*  |<----------hi---------->|<------lo----->| */
        hi = (x0 & 0x7FFFFFF8) << 1;
        hi ^= (x0 & 0x0FFFFFFF) << 4;
        lo = ((x0 & 0x7) << 1) | ((hi >> 31) & 0x1);
        lo ^= ((x0 & 0x0) << 4) | ((hi >> 28) & 0xF);
        x0 = hi | lo;
        /* loop xor on x1 */
        /* ------------------------------------------- */
        /*   30  29  28  27  ...  03 | 02  01  00  31' */
        /*   29  28  27  26  ...  02 | 01  00  31' 30' */
        /*   28  27  26  25  ...  01 | 00  31' 30' 29' */
        /* ^ 27  26  25  24  ...  00 | 31' 30' 29' 28' */
        /* ------------------------------------------- */
        /*   31' 30' 29' 28' ...  04'| 03' 02' 01' 00' */
        /*  |<----------hi---------->|<------lo----->| */
        hi = (x1 & 0x7FFFFFF8) << 1;
        hi ^= (x1 & 0x3FFFFFFC) << 2;
        hi ^= (x1 & 0x1FFFFFFE) << 3;
        hi ^= (x1 & 0x0FFFFFFF) << 4;
        lo = ((x1 & 0x7) << 1) | ((hi >> 31) & 0x1);
        lo ^= ((x1 & 0x3) << 2) | ((hi >> 30) & 0x3);
        lo ^= ((x1 & 0x1) << 3) | ((hi >> 29) & 0x7);
        lo ^= ((x1 & 0x0) << 4) | ((hi >> 28) & 0xF);
        x1 = hi | lo;
    }

    /* b0 = x0[30] ^ x0[27] */
    b0 = ((x0 >> 30) ^ (x0 >> 27)) & 0x1;
    /* x0 = (x0[31:0] shl 1) + b0 */
    x0 = (x0 << 1) | b0;
    /* b1 = x1[30] ^ x1[29] ^ x1[28] ^ x1[27] */
    b1 = ((x1 >> 30) ^ (x1 >> 29) ^ (x1 >> 28) ^ (x1 >> 27)) & 0x1;
    /* x1 = (x1[31:0] shl 1) + b1 */
    x1 = (x1 << 1) | b1;

    scramble_id = x0 ^ x1;

    return scramble_id;
}

/**
 * @brief 查询user id在帧结构的位置，id管理实现IoTE位置平铺的关键函数
 *
 * @param user_id        要查询的一组user id数组的首地址
 * @param user_id_num    要查询的一组user id个数
 * @return uc_dev_pos_t* 位置信息结构体指针，需要调用者手动释放
 */
uc_dev_pos_t *uc_wiota_query_dev_pos_by_userid(unsigned int *user_id, unsigned int user_id_num)
{
    uc_dev_pos_t *dev_pos = RT_NULL;
    unsigned int scramble_id = 0;
    unsigned int scb0 = 0;
    unsigned int scb1 = 0;
    unsigned char ul_group_num = 0;
    unsigned char group_idx = 0, burst_idx = 0, slot_idx = 0;
    unsigned int pos_num = 0;
    sub_system_config_t config = {0};

    if (RT_NULL == user_id || 0 == user_id_num)
    {
        TRACE_E("%s line %d parameter error", __FUNCTION__, __LINE__);
        return RT_NULL;
    }

    if (RT_EOK != uc_wiota_get_system_config(&config))
    {
        TRACE_E("pos query get subsystem config fail");
        return RT_NULL;
    }
    ul_group_num = 1 << config.group_number;
    uc_algo_srand(config.subsystem_id);
    scb0 = uc_algo_rand_u32();
    scb1 = uc_algo_rand_u32();
    TRACE_I("subsystem_id 0x%x scb0 0x%x, scb1 0x%x, id_num %d",
            config.subsystem_id, scb0, scb1, user_id_num);

    dev_pos = (uc_dev_pos_t *)rt_malloc(sizeof(uc_dev_pos_t) * user_id_num);
    RT_ASSERT(dev_pos);
    rt_memset(dev_pos, 0, sizeof(uc_dev_pos_t) * user_id_num);

    for (int i = 0; i < user_id_num; i++)
    {
        scramble_id = uc_query_scrambleid_by_userid(user_id[i], scb0, scb1);

        scramble_u scramble = {data : scramble_id};
        group_idx = scramble.bit_info.group_idx & (ul_group_num - 1);
        burst_idx = scramble.bit_info.burst_idx;
        slot_idx = scramble.bit_info.slot_idx;

        dev_pos[pos_num].group_idx = group_idx;
        dev_pos[pos_num].burst_idx = burst_idx;
        dev_pos[pos_num].slot_idx = slot_idx;
        pos_num++;
        // TRACE_I("user_id 0x%8x scb 0x%8x pos %d %d %d",
        //            user_id[i], scramble_id, dev_pos[i].group_idx, dev_pos[i].burst_idx, dev_pos[i].slot_idx);
    }

    return dev_pos;
}

/**
 * @brief 注册授时信息回调函数，当授时状态发生变化时，触发回调，将状态传给应用层
 *
 * @param callback
 */
void uc_wiota_register_time_service_state_callback(uc_time_service_callback callback)
{
    g_ts_cb = RT_NULL; // SPI通信不支持
}

/**
 * @brief 注册授时信息回调函数，当授时状态发生变化时，触发回调，将状态传给应用层，上报信息更多
 *
 * @param callback
 */
void uc_wiota_register_time_service_info_callback(uc_time_service_info_callback callback)
{
    g_ts_info_cb = callback;
}

/**
 * @brief 开启或关闭WIoTa帧边界校准功能，默认关闭，前提是必须开启一种授时方式
 *
 * @param is_open 开关标志，0表示关闭，1表示开启
 * @return int    0表示成功，非0表示失败
 */
int uc_wiota_set_frame_boundary_align_func(unsigned char is_open)
{
    unsigned int in_value = is_open;

    TRACE_D("set_fb_func %d", is_open);

    return uc_wiota_cmd_setup(WIOTA_FB_ALIGN_SET, &in_value, 1);
}

/**
 * @brief 根据需求开启或关闭一种授时模式，只可同时开启一种模式，最新的网关将采用同步助手的模式
 *
 * @param type    授时模式time_service_type_e
 * @param is_open 开关标识，0表示关闭，1表示开启
 * @return int    0表示成功，非0表示失败
 */
int uc_wiota_set_time_service_func(time_service_type_e type, unsigned char is_open)
{
    unsigned int in_value[2] = {type, is_open};

    TRACE_D("set_ts_func type %d, is_open %d", type, is_open);

    return uc_wiota_cmd_setup(WIOTA_TS_FUNC_SET, in_value, 2);
}

/**
 * @brief 设置授时周期，默认15分钟授时一次，范围5~60分钟
 *
 * @param cycle_min 授时周期，单位分钟
 * @return int      0表示成功，非0表示失败
 */
int uc_wiota_set_time_service_cycle(unsigned char cycle_min)
{
    unsigned int in_value = cycle_min;

    TRACE_D("set_ts_cycle cycle_min %d", cycle_min);

    return uc_wiota_cmd_setup(WIOTA_TS_CYCLE_SET, &in_value, 1);
}

/**
 * @brief 获取授时模式开关状态
 *
 * @param func_gnss GPS授时模式开关状态
 * @param func_1588 1588协议授时模式开关状态
 * @param func_sync 同步助手授时模式开关状态
 * @return int      0表示成功，非0表示失败
 */
int uc_wiota_get_time_service_func(unsigned char *func_gnss, unsigned char *func_1588, unsigned char *func_sync)
{
    int result = RT_EOK;
    unsigned int out_value[3] = {0};

    if (RT_NULL == func_gnss || RT_NULL == func_1588 || RT_NULL == func_sync)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    result = uc_wiota_cmd_query(WIOTA_TS_FUNC_GET, RT_NULL, 0, out_value, 3);
    if (result == RT_EOK)
    {
        *func_gnss = out_value[0];
        *func_1588 = out_value[1];
        *func_sync = out_value[2];
    }

    return result;
}

/**
 * @brief 启动同步授时，需要设置一种授时方式
 *
 * @return int    0表示成功，非0表示失败
 */
int uc_wiota_time_service_start(void)
{
    return uc_wiota_cmd_exec(WIOTA_TS_START);
}

/**
 * @brief 停止同步授时，一般情况下，开启后无需停止，仅GPS和1588授时模式需要，同步助手不需要调用该接口停止
 *
 * @return int 0表示成功，非0表示失败
 */
int uc_wiota_time_service_stop(void)
{
    return uc_wiota_cmd_exec(WIOTA_TS_STOP);
}

/**
 * @brief 同步1588授时时间到AP模组，1588授时模式使用，由网关1588授时模块调用
 *
 * @param sec  时钟源的整秒部分
 * @param usec 时钟源的微秒部分
 * @return int 0表示成功，非0表示失败
 */
int uc_wiota_1588_time_sync(unsigned int sec, unsigned int usec)
{
    unsigned int in_value[2] = {sec, usec};

    return uc_wiota_cmd_setup(WIOTA_1588_SYNC_SET, in_value, 2);
}

/**
 * @brief 开启或关闭同步助手的秒脉冲，可用于测试和GPS秒脉冲是否同步，验证同步时间精度，需要开启授时模式为同步助手模式，验证完后建议关闭
 *
 * @param is_open 同步助手的秒脉冲开关标识，0表示关闭，1表示开启
 * @return int    0表示成功，非0表示失败
 */
int uc_wiota_set_sync_assistant_pps(unsigned char is_open)
{
    unsigned int in_value = is_open;

    return uc_wiota_cmd_setup(WIOTA_PPS_SET, &in_value, 1);
}

/**
 * @brief 获取同步助手的秒脉冲开关标识
 *
 * @return unsigned char 同步助手的秒脉冲开关标识
 */
unsigned char uc_wiota_get_sync_assistant_pps(void)
{
    unsigned int is_open = 0;

    uc_wiota_cmd_query(WIOTA_PPS_GET, RT_NULL, 0, &is_open, 1);

    return is_open;
}

/**
 * @brief paging tx唤醒配置，该方式为发送连续唤醒信号，进入该模式低功耗的IoTE的基带会周期性检测这个信号，当检测到后实现唤醒，故该配置需要和要唤醒的IoTE配置一致
 *
 * @param config paging tx低功耗唤醒配置
 * @return int   0表示成功，非0表示失败
 */
int uc_wiota_set_paging_tx_cfg(uc_lpm_tx_cfg_t *config)
{
    int result = RT_EOK;

    if (RT_NULL == config)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    unsigned int in_value[7] = {config->freq,
                                config->spectrum_idx,
                                config->bandwidth,
                                config->symbol_length,
                                config->awaken_id,
                                config->send_time,
                                config->mode};

    result = uc_wiota_cmd_setup(WIOTA_PAGING_TX_SET, in_value, 7);
    if (result == RT_EOK)
    {
        rt_memcpy(&g_lpm_tx_cfg_info, config, sizeof(uc_lpm_tx_cfg_t));
    }

    return result;
}

/**
 * @brief 获取paging tx唤醒配置
 *
 * @return uc_lpm_tx_cfg_t* 配置结构体指针
 */
uc_lpm_tx_cfg_t *uc_wiota_get_paging_tx_cfg(void)
{
    return &g_lpm_tx_cfg_info;
}

/**
 * @brief 开启paging tx唤醒，设置完配置后，调用该接口发送单音信号，实现唤醒
 *
 * @return int 0表示成功，非0表示失败
 */
int uc_wiota_start_paging_tx(void)
{
    return uc_wiota_cmd_exec(WIOTA_PAGING_TX_START);
}

/**
 * @brief 注册WIoTa sync paging 控制消息接收回调函数
 *
 * @param callback sync paging 控制消息接收回调函数指针
 */
void uc_wiota_register_sync_paging_callback(uc_paging_ctrl_callback callback)
{
    g_sync_paging_ctrl_cb = callback;
}

/**
 * @brief 发送失败处理函数
 *
 * @param user_id  单播时为IoTE的user id，广播时或OTA则为uc_bc_mode_e，组播时则为组播id
 * @param callback paging结果回调函数指针
 */
static void uc_wiota_report_paging_fail_by_cb(unsigned int user_id, uc_paging_callback callback)
{
    if (callback != RT_NULL)
    {
        uc_paging_recv_t result = {0};

        result.user_id = user_id;
        result.result = UC_OP_FAIL;
        callback(&result);
    }
}

/**
 * @brief 发送sync paging请求唤醒进入sync paging模式睡眠的IoTE，前提会必须收到fn_index，否则会导致唤醒失败
 *
 * @param paging_info  sync paging信息结构体指针
 * @param callback     sync paging执行结果回调，传入RT_NULL时表示阻塞调用，非RT_NULL时表示非阻塞调用，建议非阻塞调用
 * @return uc_result_e sync paging执行结果，只有成功
 */
uc_result_e uc_wiota_sync_paging(uc_paging_info_t *paging_info, uc_paging_callback callback)
{
    uc_result_e result = UC_OP_FAIL;
    paging_res_t *paging_res = RT_NULL;
    paging_info_t paging_send = {0};
    struct cmd_send_para cmd_info = {0};

    if (RT_NULL == paging_info)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        uc_wiota_report_paging_fail_by_cb(paging_info->user_id, callback);
        return UC_OP_FAIL;
    }
    paging_send.cmd_header.cmd_type = CMD_TYPE_SEND;
    paging_send.cmd_header.cmd = WIOTA_SYNC_PAGING;
    paging_send.cmd_header.channel = AP_CHIP_INDEX;
    paging_send.user_id = paging_info->user_id;
    paging_send.fn_index = paging_info->fn_index;
    paging_send.detection_period = paging_info->detection_period;
    paging_send.send_round = paging_info->send_round;
    paging_send.continue_fn = paging_info->continue_fn;
    paging_send.is_block = (RT_NULL == callback ? 1 : 0);

    cmd_info.cmd_header = &paging_send.cmd_header;
    cmd_info.para_num = 1;
    cmd_info.para_ptr[0] = ((unsigned char *)&paging_send) + sizeof(cmd_header_t);
    cmd_info.para_len[0] = sizeof(paging_info_t) - sizeof(cmd_header_t);

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        if (callback == RT_NULL)
        {
            paging_res = uc_wiota_recv_response(CMD_TYPE_SEND, WIOTA_SYNC_PAGING, WIOTA_CMD_RESP_TIMEOUT);
            if (paging_res)
            {
                if (paging_send.cmd_header.result == WIOTA_CMD_EXEC_SUC &&
                    paging_send.cmd_header.cmd_type == paging_res->cmd_header.cmd_type &&
                    paging_send.cmd_header.cmd == paging_res->cmd_header.cmd &&
                    paging_send.cmd_header.channel == paging_res->cmd_header.channel)
                {
                    result = paging_res->result;
                }
                else
                {
                    result = UC_OP_FAIL;
                }
                uc_wiota_sub_data_free(paging_res);
            }
            else
            {
                result = UC_OP_TIMEOUT;
            }
        }
        else
        {
            g_sync_paging_cb = callback;
            result = UC_OP_SUCC;
        }
    }
    else
    {
        result = UC_OP_FAIL;
        uc_wiota_report_paging_fail_by_cb(paging_info->user_id, callback);
    }

    return result;
}

/**
 * @brief 获取某帧结构位置上正在进行sync paging的IoTE个数
 *
 * @param group_idx      帧结构group位置
 * @param subframe_idx   帧结构子帧位置
 * @return unsigned char 正在进行sync paging的IoTE个数，0xff表示失败
 */
unsigned char uc_wiota_get_sync_paging_num(unsigned char group_idx, unsigned char subframe_idx)
{
    unsigned int in_value[2] = {group_idx, subframe_idx};
    unsigned int sync_paging_num = 0;

    if (group_idx > 8 || subframe_idx > 8)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return 0xff;
    }

    uc_wiota_cmd_query(WIOTA_PPS_GET, in_value, 2, &sync_paging_num, 1);

    return sync_paging_num;
}

/**
 * @brief AP单独发送单音信号
 *
 * @param is_open 开启发送单音信号
 * @return int    0表示成功，非0表示失败
 */
int uc_wiota_set_single_tone(unsigned int is_open)
{
    unsigned int in_value = is_open;

    return uc_wiota_cmd_setup(WIOTA_SINGLE_TONE_SET, &in_value, 1);
}

/**
 * @brief 设置协议层单播重发次数
 *
 * @param resend_times
 * @return int
 */
int uc_wiota_set_sm_resend_times(unsigned char resend_times)
{
    unsigned int in_value = resend_times;

    return uc_wiota_cmd_setup(WIOTA_SM_RESEND_TIMES_SET, &in_value, 1);
}

/**
 * @brief AP帧号刷新回调注册
 *
 * @param callback 回调函数指针
 */
void uc_wiota_register_fn_refresh_callback(uc_fn_refresh_callback callback)
{
    g_fn_refresh_cb = callback;

    uc_wiota_cmd_exec(WIOTA_REG_FN_REFRESH_CB);
}

/**
 * @brief 让终端主动处于连接态并根据参数接收和发送短消息
 *
 * @param rs_fn  定帧接收结构体指针
 *
 * @return uc_result_e    0表示成功，非0表示失败
 */
uc_result_e uc_wiota_recv_send_sm_by_fn(recv_send_by_fn_t *rs_fn)
{
    unsigned int data_id = data_id = (unsigned int)rs_fn->para;
    unsigned int is_block = 0;
    uc_result_e result = UC_OP_FAIL;
    rs_sm_by_fn_ex_t sf_rs = {0};
    struct cmd_send_para cmd_info = {0};
    send_res_t *send_recv = RT_NULL;

    if (RT_NULL == rs_fn || 0 == rs_fn->user_id || 0 == rs_fn->recv_fns)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        uc_wiota_report_send_fail_by_cb(rs_fn->user_id, data_id, rs_fn->callback);
        return UC_OP_FAIL;
    }

    sf_rs.cmd_header.cmd_type = CMD_TYPE_SEND;
    sf_rs.cmd_header.cmd = WIOTA_RS_SM_BY_FN;
    sf_rs.cmd_header.channel = AP_CHIP_INDEX;
    sf_rs.user_id = rs_fn->user_id;
    sf_rs.data_id = data_id;
    sf_rs.start_recv_fn = rs_fn->start_recv_fn;
    sf_rs.recv_fns = rs_fn->recv_fns;
    sf_rs.send_fns = rs_fn->send_fns;
    sf_rs.data_len = rs_fn->data_len;

    cmd_info.cmd_header = &sf_rs.cmd_header;
    cmd_info.para_num = 1;
    cmd_info.para_ptr[0] = ((unsigned char *)&sf_rs) + sizeof(cmd_header_t);
    cmd_info.para_len[0] = sizeof(rs_sm_by_fn_ex_t) - sizeof(cmd_header_t);

    if (rs_fn->data && rs_fn->data_len)
    {
        cmd_info.para_num += 1;
        cmd_info.para_ptr[1] = rs_fn->data;
        cmd_info.para_len[1] = rs_fn->data_len;
    }

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
#ifdef ULDL_STATISTICS
        g_spi_com.dl_send_total_cnt++;
#endif
        if (data_id != 0)
        {
            g_spi_com.data_id_send_cnt++;
        }
        if (rs_fn->callback)
        {
            g_send_cb[WIOTA_SEND_DATA] = rs_fn->callback;
        }
        result = UC_OP_SUCC;
    }
    else
    {
        result = UC_OP_FAIL;
        uc_wiota_report_send_fail_by_cb(rs_fn->user_id, data_id, rs_fn->callback);
    }

    return result;
}

/**
 * @brief 设置aagc
 *
 * @param aagc_idx  aagc索引
 * @return int     0表示成功，非0表示失败
 */
int uc_wiota_set_aagc_idx(unsigned char aagc_idx)
{
    unsigned int in_value[1] = {aagc_idx};

    // 范围0~15
    if (aagc_idx > 15)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    return uc_wiota_cmd_setup(WIOTA_AAGC_IDX_SET, in_value, 1);
}

/**
 * @brief 开启bnack冲突解决功能，开启该功能后当同一子帧有多个终端同时传输时，竞争接入的终端会优先执行，竞争失败的终端会自动回避，能极大的提高多终端发送的成功率
 *
 * @param is_open  开启标志，0表示关闭，1表示开启
 * @return int     0表示成功，非0表示失败
 */
int uc_wiota_set_bnack_func(unsigned char is_open)
{
    unsigned int in_value[1] = {is_open};

    if (is_open > 1)
    {
        is_open = 1;
    }

    return uc_wiota_cmd_setup(WIOTA_BNACK_FUNC_SET, in_value, 1);
}

#ifdef WIOTA_SUBF_MODE_SUPPORT
/**
 * @brief 设置子帧模式配置，协议栈根据此配置，发送和编码子帧数据
 *
 * @param subf_cfg   子帧模式配置
 * @return int       0表示成功，非0表示失败
 */
int uc_wiota_set_subframe_mode_cfg(uc_subf_cfg_t *subf_cfg)
{
    if (RT_NULL == subf_cfg)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    unsigned int in_value[2] = {subf_cfg->block_size, subf_cfg->send_round};

    TRACE_D("set subf_mode_cfg %d, %d", subf_cfg->block_size, subf_cfg->send_round);

    return uc_wiota_cmd_setup(WIOTA_SUBF_MODE_CFG_SET, in_value, 2);
}

/**
 * @brief 设置上行子帧连续接收模式，用于语音接收，可根据接收数据类型来判断是否为子帧数据
 *
 * @param subf_mode  是否打开标志，0表示关闭，1表示一帧一包语音数据，2表示一帧两包语音数据
 * @param user_id    要打开子帧连续接收模式的终端ID
 * @param rach_delay 接入delay
 * @return int       0表示成功，非0表示失败
 */
int uc_wiota_set_ul_subframe_mode(unsigned char subf_mode, unsigned int user_id, unsigned char rach_delay)
{
    unsigned int in_value[3] = {subf_mode, user_id, rach_delay};

    if (user_id == 0 || subf_mode > 2)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    TRACE_D("set subf_mode 0x%x to %d %d", user_id, subf_mode, rach_delay);

    return uc_wiota_cmd_setup(WIOTA_SUBF_MODE_SET, in_value, 3);
}

/**
 * @brief 添加下行子帧数据，语音传输专用，除子帧6以外的所有下行子帧都可以发，且只需控制一次广播帧开始，发送数据大小为当前mcs的最大且为语音编码块的整数倍
 *
 * @param data      要发送的下行子帧数据指针
 * @param data_len  要发送的下行子帧数据长度
 * @return fn       上行子帧数据接收的帧号，下行回填
 * @return int      0表示成功，非0表示失败
 */
int uc_wiota_add_dl_subframe_data(unsigned char *data, unsigned char data_len, unsigned char fn)
{
    int result = RT_ERROR;
    subf_data_ex_t subf_data = {0};
    struct cmd_send_para cmd_info = {0};

    if (RT_NULL == data || 0 == data_len || data_len > 72)
    {
        TRACE_E("%s line %d para error", __FUNCTION__, __LINE__);
        return -1;
    }

    subf_data.cmd_header.cmd_type = CMD_TYPE_OTHER;
    subf_data.cmd_header.cmd = WIOTA_ADD_SUBF_DATA;
    subf_data.cmd_header.channel = AP_CHIP_INDEX;
    subf_data.data_len = data_len;
    subf_data.fn = fn;

    cmd_info.cmd_header = &subf_data.cmd_header;
    cmd_info.para_num = 2;
    cmd_info.para_ptr[0] = ((unsigned char *)&subf_data) + sizeof(cmd_header_t);
    cmd_info.para_len[0] = sizeof(subf_data_ex_t) - sizeof(cmd_header_t);
    cmd_info.para_ptr[1] = data;
    cmd_info.para_len[1] = data_len;

    if (uc_wiota_send_data_to_ap(&cmd_info) > 0)
    {
        // 无需等待结果，直接返回
        result = RT_EOK;
    }

    return result;
}
#endif